Files
upstream-corteza/server/vendor/github.com/crewjam/httperr
upstream-mirror 7e385fc126
Some checks failed
SonarQube / SonarQube (push) Failing after 5m28s
E2e test on main branch push / server and client test (map[name:admin port:8080]) (push) Failing after 3m33s
Server and lib tests / Library test (js) (push) Failing after 1m36s
Server and lib tests / Library test (vue) (push) Successful in 2m24s
Server and lib tests / Web client test (admin) (push) Successful in 2m52s
Server and lib tests / Web client test (compose) (push) Successful in 3m23s
Server and lib tests / Web client test (discovery) (push) Successful in 2m49s
Server and lib tests / Web client test (one) (push) Successful in 2m52s
Server and lib tests / Web client test (privacy) (push) Successful in 2m44s
Server and lib tests / Web client test (reporter) (push) Successful in 2m56s
Server and lib tests / Web client test (workflow) (push) Successful in 3m0s
Server and lib tests / Server tests (push) Failing after 4m55s
E2e test on main branch push / notify-failure (push) Failing after 9s
Server and lib tests / Send matrix testing notification (push) Successful in 2s
Mark stale issues and pull requests / stale (push) Successful in 40s
snapshot of https://github.com/cortezaproject/corteza.git (2024.9.x, 2026-09-18; blobs > 131072B omitted)
2026-09-18 01:22:23 +05:00
..

httperr

GoDoc

Build Status

Package httperr provides utilities for handling error conditions in http clients and servers.

Client

This package provides an http.Client that returns errors for requests that return a status code >= 400. It lets you turn code like this:

func GetFoo() {
    req, _ := http.NewRequest("GET", "https://api.example.com/foo", nil)
    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        return err
    }
    if resp.StatusCode >= 400 {
        return nil, fmt.Errorf("api call failed: %d", resp.StatusCode)
    }
    // ....
}

Into code like this:

func GetFoo() {
    req, _ := http.NewRequest("GET", "https://api.example.com/foo", nil)
    resp, err := httperr.Client().Do(req)
    if err != nil {
        return nil, err
    }
    // ....
}

Wow, three whole lines. Life changing, eh? But wait, there's more!

You can have the client parse structured errors returned from an API:


type APIError struct {
    Message string `json:"message"`
    Code string `json:"code"`
}

func (a APIError) Error() string {
    // APIError must implement the Error interface
    return fmt.Sprintf("%s (code %d)", a.Message, a.Code)
}

func GetFoo() {
    client := httperr.Client(http.DefaultClient, httperr.JSON(APIError{}))

    req, _ := http.NewRequest("GET", "https://api.example.com/foo", nil)
    resp, err := client.Do(req)
    if err != nil {
        // If the server returned a status code >= 400, and the response was valid
        // JSON for APIError, then err is an *APIErr.
        return nil, err
    }
    // ....
}

Server

Error handling in Go's http.Handler and http.HandlerFunc can be tricky. I often found myself wishing that we could just return an err and be done with things.

This package provides an adapter function which turns:

func (s *Server) getUser(w http.ResponseWriter, r *http.Request) {
    remoteUser, err := s.Auth.RequireUser(w, r)
    if err != nil {
        http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
        return
    }

    user, err := s.Storage.Get(remoteUser.Name)
    if err != nil {
        log.Printf("ERROR: cannot fetch user: %s", err)
        http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
        return
    }
    json.NewEncoder(w).Encode(user)
}

Into this:

func (s *Server) getUser(w http.ResponseWriter, r *http.Request) error {
    remoteUser, err := s.Auth.RequireUser(w, r)
    if err != nil {
        return httperr.Unauthorized
    }

    user, err := s.Storage.Get(remoteUser.Name)
    if err != nil {
        return err
    }
    return json.NewEncoder(w).Encode(user)
}

Life changing? Probably not, but it seems to remove a lot of redundancy and make control flow in web servers simpler.

You can also wrap your calls with middleware that allow you to provide custom handling of errors that are returned from your handlers, but also >= 400 status codes issued by handlers that don't return errors.

htmlErrorTmpl := template.Must(template.New("err").Parse(errorTemplate))
handler := httperr.Middleware{
    OnError: func(w http.ResponseWriter, r *http.Request, err error) error {
        log.Printf("REQUEST ERROR: %s", err)
        if acceptHeaderContainsTextHTML(r) {
            htmlErrorTmpl.Execute(w, struct{ Error error }{Error: err})
            return nil // nil means we've handled the error
        }
        return err // fall back to the default
    },
    Handler: httperr.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
        if r.Method != "POST" {
            return httperr.MethodNotAllowed
        }
        var reqBody RequestBody
        if err := json.NewDecoder(r.Body).Decode(&reqBody); err != nil {
            return httperr.Public{
                StatusCode: http.StatusBadRequest,
                Err:        err,
            }
        }

        if reqBody.Count <= 0 {
            // The client won't see this, instead OnError will be called with a httperr.Response containing
            // the response. The OnError function can decide to write the error, or replace it with it's own.
            w.WriteHeader(http.StatusConflict)
            fmt.Fprintln(w, "an obscure internal error happened, but the user doesn't want to see this.")
            return nil
        }

        // ...
        return nil
    }),
}