package server import ( "encoding/json" "log" "net/http" "net/url" ) type ApiError struct { Err string Status int } func (e ApiError) Error() string { return e.Err } type Api struct { mux *http.ServeMux host string } func NewApi(host string) Api { return Api{ mux: http.NewServeMux(), host: host, } } func (a Api) Run() error { log.Println("Server is running at ", a.host) return http.ListenAndServe(a.host, a.mux) } func (a Api) Group(path string) group { return group{path: path, api: &a} } type group struct { api *Api path string } func (g group) Group(path string) group { p, _ := url.JoinPath(g.path, path) return group{path: p, api: g.api} } type apiFunc func(http.ResponseWriter, *http.Request) error func (g group) Endpoint(path string, f apiFunc) *group { p, _ := url.JoinPath(g.path, path) log.Println("New path registered: ", p, f) g.api.mux.HandleFunc(p, func(w http.ResponseWriter, r *http.Request) { if err := f(w, r); err != nil { if e, ok := err.(ApiError); ok { WriteJSON(w, e.Status, e) return } WriteJSON(w, 500, ApiError{Err: err.Error(), Status: http.StatusInternalServerError}) return } }) return &g } func StatusAndContent(w http.ResponseWriter, status int, contentType string) { w.Header().Add("Content-Type", contentType) w.WriteHeader(status) } func WriteJSON(w http.ResponseWriter, status int, v any) error { StatusAndContent(w, status, "application/json") return json.NewEncoder(w).Encode(v) } func WritePlain(w http.ResponseWriter, status int, text string) error { StatusAndContent(w, status, "text/plain") _, err := w.Write([]byte(text)) return err } func WriteBlob(w http.ResponseWriter, status int, contentType string, blob []byte) error { StatusAndContent(w, status, contentType) _, err := w.Write(blob) return err }