package server import ( "encoding/json" "io" "log" "net/http" "runtime/debug" "github.com/gorilla/mux" ) type ApiError struct { Err string Status int } func (e ApiError) Error() string { return e.Err } type apiFunc func(Context) error func MakeHTTPHandler(f apiFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx := Context{w, r} if err := f(ctx); err != nil { if e, ok := err.(ApiError); ok { log.Println("API ERROR: ", e.Error(), e.Status) ctx.WriteJSON(e.Status, e) return } log.Println("INTERNAL ERROR: ", err) debug.PrintStack() ctx.WriteJSON(500, &ApiError{Err: "internal error", Status: http.StatusInternalServerError}) return } } } type Context struct { W http.ResponseWriter R *http.Request } func (c Context) GetVar(name string) string { vars := mux.Vars(c.R) val, ok := vars[name] if ok { return val } return "" } func (c Context) Header(header string, value string) *Context { c.W.Header().Add(header, value) return &c } func (c Context) ContentType(contentType string) *Context { return c.Header("Content-Type", contentType) } func (c Context) Status(status int) *Context { c.W.WriteHeader(status) return &c } func (c Context) WriteJSON(status int, v any) error { c.ContentType("application/json").Status(status) return json.NewEncoder(c.W).Encode(v) } func (c Context) WritePlain(status int, text string) error { c.ContentType("text/plain").Status(status) _, err := c.W.Write([]byte(text)) return err } func (c Context) WriteBlob(status int, contentType string, blob []byte) error { c.ContentType(contentType).Status(status) _, err := c.W.Write(blob) return err } func DecodeJSON(r io.Reader, v any) error { return json.NewDecoder(r).Decode(v) }