@@ -3,17 +3,25 @@ package httpx
33import (
44 "context"
55 "encoding/json"
6+ "errors"
7+ "fmt"
68 "net/http"
79
8- "github.com/pkg/errors"
910 "github.com/remind101/pkg/reporter"
1011)
1112
13+ // Error reports an error and encodes it to the http.ResponseWriter
1214func Error (ctx context.Context , err error , rw http.ResponseWriter , r * http.Request ) {
1315 reporter .Report (ctx , err )
1416 EncodeError (err , rw )
1517}
1618
19+ // ErrorWithStatus reports an error with a specific status code
20+ func ErrorWithStatus (ctx context.Context , err error , status int , rw http.ResponseWriter , r * http.Request ) {
21+ reporter .Report (ctx , err )
22+ EncodeErrorWithStatus (err , status , rw )
23+ }
24+
1725type temporaryError interface {
1826 Temporary () bool // Is the error temporary?
1927}
@@ -26,9 +34,15 @@ type statusCoder interface {
2634 StatusCode () int
2735}
2836
37+ // EncodeError encodes an error to the http.ResponseWriter
2938func EncodeError (err error , rw http.ResponseWriter ) {
39+ EncodeErrorWithStatus (err , ErrorStatusCode (err ), rw )
40+ }
41+
42+ // EncodeErrorWithStatus encodes an error with a specific status code
43+ func EncodeErrorWithStatus (err error , status int , rw http.ResponseWriter ) {
3044 rw .Header ().Set ("Content-Type" , "application/json" )
31- rw .WriteHeader (ErrorStatusCode ( err ) )
45+ rw .WriteHeader (status )
3246
3347 errorResp := map [string ]string {
3448 "error" : err .Error (),
@@ -37,18 +51,71 @@ func EncodeError(err error, rw http.ResponseWriter) {
3751 json .NewEncoder (rw ).Encode (errorResp )
3852}
3953
54+ // ErrorStatusCode returns an appropriate HTTP status code based on the error type
4055func ErrorStatusCode (err error ) int {
41- rootErr := errors . Cause ( err )
42- if e , ok := rootErr .( statusCoder ); ok {
43- return e .StatusCode ()
56+ var sc statusCoder
57+ if errors . As ( err , & sc ) {
58+ return sc .StatusCode ()
4459 }
45- if e , ok := rootErr .(temporaryError ); ok && e .Temporary () {
60+
61+ var te temporaryError
62+ if errors .As (err , & te ) && te .Temporary () {
4663 return http .StatusServiceUnavailable
4764 }
4865
49- if e , ok := rootErr .(timeoutError ); ok && e .Timeout () {
66+ var to timeoutError
67+ if errors .As (err , & to ) && to .Timeout () {
5068 return http .StatusServiceUnavailable
5169 }
5270
5371 return http .StatusInternalServerError
5472}
73+
74+ // NewError creates a new error with a message and optional key-value pairs
75+ func NewError (msg string , keyvals ... interface {}) error {
76+ return & httpError {
77+ msg : msg ,
78+ keyvals : keyvals ,
79+ }
80+ }
81+
82+ // httpError is a structured error type that can include key-value pairs
83+ type httpError struct {
84+ msg string
85+ keyvals []interface {}
86+ status int
87+ err error
88+ }
89+
90+ // Error implements the error interface
91+ func (e * httpError ) Error () string {
92+ if e .err != nil {
93+ return fmt .Sprintf ("%s: %s" , e .msg , e .err .Error ())
94+ }
95+ return e .msg
96+ }
97+
98+ // WithStatus sets the HTTP status code for the error
99+ func (e * httpError ) WithStatus (status int ) * httpError {
100+ e .status = status
101+ return e
102+ }
103+
104+ // WithError wraps another error
105+ func (e * httpError ) WithError (err error ) * httpError {
106+ e .err = err
107+ return e
108+ }
109+
110+ // StatusCode implements the statusCoder interface
111+ func (e * httpError ) StatusCode () int {
112+ if e .status != 0 {
113+ return e .status
114+ }
115+ return http .StatusInternalServerError
116+ }
117+
118+ // Unwrap implements the errors.Wrapper interface for Go 1.13+ error unwrapping
119+ func (e * httpError ) Unwrap () error {
120+ return e .err
121+ }
0 commit comments