From 6f10031f64f8841e95a2fd7eea79fc832eae7f49 Mon Sep 17 00:00:00 2001 From: "Jonathan R. Warden" Date: Sun, 6 Nov 2022 13:04:32 -0700 Subject: [PATCH] - move main line of development to /v1. /v2 is in the v2 branch. The only difference is the second optional argment ot onPanic. - errors produced by PanicMiddleware is always an httperrror.Panic - add PanicMiddleware example to docs - update PanicMiddleware tests --- README.md | 125 ++++++++++++++++++++++++++++++++---------- error_test.go | 2 +- go.mod | 2 +- httperror.go | 2 +- panic.go | 60 ++++++++++++-------- request_test.go | 56 ++++++++++++++----- standardmiddleware.go | 6 +- 7 files changed, 181 insertions(+), 72 deletions(-) diff --git a/README.md b/README.md index 9cb89a2..cf95141 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ Package httperror is for writing HTTP handlers that return errors instead of handling them directly. -- installation: `go get github.com/johnwarden/httperror/v2` -- [godoc](https://pkg.go.dev/github.com/johnwarden/httperror/v2) +- installation: `go get github.com/johnwarden/httperror` +- [godoc](https://pkg.go.dev/github.com/johnwarden/httperror) ## Overview @@ -34,7 +34,7 @@ return an error. Although there is no explicit error handling code in this example, if you run it and fetch http://localhost:8080/hello without a `name` URL parameter, an appropriate plain-text 400 Bad Request page will be served. -This is because `helloHandler` is converted into a [httperror.HandlerFunc](https://pkg.go.dev/github.com/johnwarden/httperror/v2#HandlerFunc), which implements the standard [http.Handler](https://pkg.go.dev/net/http#Handler) interface, but the 400 +This is because `helloHandler` is converted into a [httperror.HandlerFunc](https://pkg.go.dev/github.com/johnwarden/httperror#HandlerFunc), which implements the standard [http.Handler](https://pkg.go.dev/net/http#Handler) interface, but the 400 Bad Raquest error returned by `helloHandler` will be handled by a default error handler that serves an appropriate error page. @@ -46,11 +46,11 @@ handler that serves an appropriate error page. - reduce risk of "naked returns" as described by Preslav Rachev's in [I Don't Like Go's Default HTTP Handlers](https://preslav.me/2022/08/09/i-dont-like-golang-default-http-handlers/) - middleware can inspect errors, extract status codes, add context, and appropriately log and handle errors -This package is built based on the philosophy that HTTP frameworks are not needed in Go: the [net/http](https://pkg.go.dev/net/http) package, and the various router, middleware, and templating libraries that are compatible with it, are sufficient. However, the lack of an error return value in the signature of standard http handler functions is perhaps a small design flaw in the http package. This package addresses this without tying you to a framework: [httperror.Handler](https://pkg.go.dev/github.com/johnwarden/httperror/v2#Handler) **is** an [http.Handler](https://pkg.go.dev/net/http#Handler). You can [apply standard http Handler](/#applying-standard-middleware) middleware to it. And your handler functions look exactly as they would look if [net/http](https://pkg.go.dev/net/http) had been designed differently. +This package is built based on the philosophy that HTTP frameworks are not needed in Go: the [net/http](https://pkg.go.dev/net/http) package, and the various router, middleware, and templating libraries that are compatible with it, are sufficient. However, the lack of an error return value in the signature of standard http handler functions is perhaps a small design flaw in the http package. This package addresses this without tying you to a framework: [httperror.Handler](https://pkg.go.dev/github.com/johnwarden/httperror#Handler) **is** an [http.Handler](https://pkg.go.dev/net/http#Handler). You can [apply standard http Handler](/#applying-standard-middleware) middleware to it. And your handler functions look exactly as they would look if [net/http](https://pkg.go.dev/net/http) had been designed differently. ## Custom Error Handlers -Use [WrapHandlerFunc](https://pkg.go.dev/github.com/johnwarden/httperror/v2#WrapHandlerFunc) to add a custom error handler. +Use [WrapHandlerFunc](https://pkg.go.dev/github.com/johnwarden/httperror#WrapHandlerFunc) to add a custom error handler. func customErrorHandler(w http.ResponseWriter, e error) { @@ -81,13 +81,11 @@ Returning errors from functions enable some new middleware patterns. Here is an example of custom middleware that [logs errors](#example-log-middleware). -[PanicMiddleware](https://pkg.go.dev/github.com/johnwarden/httperror/v2#PanicMiddleware) -and [XPanicMiddleware](https://pkg.go.dev/github.com/johnwarden/httperror/v2#XPanicMiddleware) +[PanicMiddleware](https://pkg.go.dev/github.com/johnwarden/httperror#PanicMiddleware) +and [XPanicMiddleware](https://pkg.go.dev/github.com/johnwarden/httperror#XPanicMiddleware) are simple middleware functions that convert panics to errors. This ensures users are served an appropriate 500 error response on panic instead of an empty response. And it allows -middleware to appropriately inspects, count, and log panics as they do other errors. These functions -accept a panic handler as an optional second argument. This can be used for example to trigger a shutdown on panic. - +middleware to appropriately inspects, count, and log panics as they do other errors. ## Extracting, Embedding, and Comparing HTTP Status Codes @@ -113,26 +111,26 @@ accept a panic handler as an optional second argument. This can be used for exam ## Public Error Messages -The default error handler, [DefaultErrorHandler](https://pkg.go.dev/github.com/johnwarden/httperror/v2#DefaultErrorHandler) will +The default error handler, [DefaultErrorHandler](https://pkg.go.dev/github.com/johnwarden/httperror#DefaultErrorHandler) will not show the full error string to users, because these often contain stack traces or other implementation details that should not be exposed to the public. But if the error value has an embedded public error message, the error handler will display this to the user. To embed a public error message, -create an error using [NewPublic](https://pkg.go.dev/github.com/johnwarden/httperror/v2#NewPublic) or [PublicErrorf](https://pkg.go.dev/github.com/johnwarden/httperror/v2#PublicErrorf) instead of [New](https://pkg.go.dev/github.com/johnwarden/httperror/v2#New) or [Errorf](https://pkg.go.dev/github.com/johnwarden/httperror/v2#Errorf): +create an error using [NewPublic](https://pkg.go.dev/github.com/johnwarden/httperror#NewPublic) or [PublicErrorf](https://pkg.go.dev/github.com/johnwarden/httperror#PublicErrorf) instead of [New](https://pkg.go.dev/github.com/johnwarden/httperror#New) or [Errorf](https://pkg.go.dev/github.com/johnwarden/httperror#Errorf): e := httperror.NewPublic(404, "Sorry, we can't find a product with this ID") -Public error messages are extracted by [PublicMessage](https://pkg.go.dev/github.com/johnwarden/httperror/v2#PublicMessage): +Public error messages are extracted by [PublicMessage](https://pkg.go.dev/github.com/johnwarden/httperror#PublicMessage): m := httperror.PublicMessage(e) -If your custom error type defines a `PublicMessage() string` method, then [PublicMessage](https://pkg.go.dev/github.com/johnwarden/httperror/v2#PublicMessage) will call and return the value from that method. +If your custom error type defines a `PublicMessage() string` method, then [PublicMessage](https://pkg.go.dev/github.com/johnwarden/httperror#PublicMessage) will call and return the value from that method. ## Generic Handler and HandlerFunc Types -This package defines generic versions of [httperror.Handler](https://pkg.go.dev/github.com/johnwarden/httperror/v2#Handler) and -[httperror.HandlerFunc](https://pkg.go.dev/github.com/johnwarden/httperror/v2#HandlerFunc) that accept a -third parameter of any type. These are [httperror.XHandler](https://pkg.go.dev/github.com/johnwarden/httperror/v2#XHandler) and -[httperror.XHandlerFunc](https://pkg.go.dev/github.com/johnwarden/httperror/v2#XHandlerFunc). +This package defines generic versions of [httperror.Handler](https://pkg.go.dev/github.com/johnwarden/httperror#Handler) and +[httperror.HandlerFunc](https://pkg.go.dev/github.com/johnwarden/httperror#HandlerFunc) that accept a +third parameter of any type. These are [httperror.XHandler](https://pkg.go.dev/github.com/johnwarden/httperror#XHandler) and +[httperror.XHandlerFunc](https://pkg.go.dev/github.com/johnwarden/httperror#XHandlerFunc). The third parameter can contain parsed request parameters, authorized user IDs, and other information required by handlers. For example, the @@ -153,25 +151,25 @@ accepted its parameters as a struct. ## Use with Other Routers, Frameworks, and Middleware -Many routers and frameworks use a custom type for passing parsed request parameters or a request context. A generic [httperror.XHandler](https://pkg.go.dev/github.com/johnwarden/httperror/v2#XHandler) can accept a third argument of any type, so you can write handlers that work with your preferred framework but that also return errors. For example: +Many routers and frameworks use a custom type for passing parsed request parameters or a request context. A generic [httperror.XHandler](https://pkg.go.dev/github.com/johnwarden/httperror#XHandler) can accept a third argument of any type, so you can write handlers that work with your preferred framework but that also return errors. For example: var ginHandler httperror.XHandler[*gin.Context] = func(w http.ResponseWriter, r *http.Request, c *gin.Context) error { ... } var httprouterHandler httperror.XHandler[httprouter.Params] = func(w http.ResponseWriter, r *http.Request, p httprouter.Params) error { ... } See [this example](#example-httprouter) of using this package pattern with a [github.com/julienschmidt/httprouter](https://github.com/julienschmidt/httprouter). -One advantages of writing functions this way, other than that they can return errors instead of handling them, is that you can apply generic middleware written for [httperror.XHandler](https://pkg.go.dev/github.com/johnwarden/httperror/v2#XHandler)s, such as [PanicMiddleware](https://pkg.go.dev/github.com/johnwarden/httperror/v2#PanicMiddleware) for converting panics to errors. In fact, this package makes it easy to apply middleware that was not written for any particular router or framework. +One advantages of writing functions this way, other than that they can return errors instead of handling them, is that you can apply generic middleware written for [httperror.XHandler](https://pkg.go.dev/github.com/johnwarden/httperror#XHandler)s, such as [PanicMiddleware](https://pkg.go.dev/github.com/johnwarden/httperror#PanicMiddleware) for converting panics to errors. In fact, this package makes it easy to apply middleware that was not written for any particular router or framework. ### Applying Standard Middleware -You can apply middleware written for standard HTTP handlers to an [httperror.Handler](https://pkg.go.dev/github.com/johnwarden/httperror/v2#Handler) or an [httperror.XHandler](https://pkg.go.dev/github.com/johnwarden/httperror/v2#XHandler), because they both implement the [http.Handler](https://pkg.go.dev/net/http#Handler) interface. See the [standard middleware example](#example-standard-middleware). +You can apply middleware written for standard HTTP handlers to an [httperror.Handler](https://pkg.go.dev/github.com/johnwarden/httperror#Handler) or an [httperror.XHandler](https://pkg.go.dev/github.com/johnwarden/httperror#XHandler), because they both implement the [http.Handler](https://pkg.go.dev/net/http#Handler) interface. See the [standard middleware example](#example-standard-middleware). -However, the handler returned from a standard middleware wrapper will be an [http.Handler](https://pkg.go.dev/net/http#Handler), and will therefore not be able to return an error or accept additional parameters. Instead, use [ApplyStandardMiddleware](https://pkg.go.dev/github.com/johnwarden/httperror/v2#ApplyStandardMiddleware) and [XApplyStandardMiddleware](https://pkg.go.dev/github.com/johnwarden/httperror/v2#ApplyStandardMiddleware), which return an [httperror.Handler](https://pkg.go.dev/github.com/johnwarden/httperror/v2#Handler) or an [httperror.XHandler](https://pkg.go.dev/github.com/johnwarden/httperror/v2#XHandler) respectively. You can see an example of this in the [httprouter example](#example-httprouter). +However, the handler returned from a standard middleware wrapper will be an [http.Handler](https://pkg.go.dev/net/http#Handler), and will therefore not be able to return an error or accept additional parameters. Instead, use [ApplyStandardMiddleware](https://pkg.go.dev/github.com/johnwarden/httperror#ApplyStandardMiddleware) and [XApplyStandardMiddleware](https://pkg.go.dev/github.com/johnwarden/httperror#ApplyStandardMiddleware), which return an [httperror.Handler](https://pkg.go.dev/github.com/johnwarden/httperror#Handler) or an [httperror.XHandler](https://pkg.go.dev/github.com/johnwarden/httperror#XHandler) respectively. You can see an example of this in the [httprouter example](#example-httprouter). ## Similar Packages -[github.com/caarlos0/httperr](https://github.com/caarlos0/httperr) uses a very similar approach, for example the definition of: [httperr.HandlerFunc](https://pkg.go.dev/github.com/caarlos0/httperr#HandlerFunc) and [httperror.HandlerFunc](https://pkg.go.dev/github.com/johnwarden/httperror/v2#HandlerFunc) are identical. I have this package to be mostly compatible with this [httperr](https://github.com/caarlos0/httperr). +[github.com/caarlos0/httperr](https://github.com/caarlos0/httperr) uses a very similar approach, for example the definition of: [httperr.HandlerFunc](https://pkg.go.dev/github.com/caarlos0/httperr#HandlerFunc) and [httperror.HandlerFunc](https://pkg.go.dev/github.com/johnwarden/httperror#HandlerFunc) are identical. I have this package to be mostly compatible with this [httperr](https://github.com/caarlos0/httperr). ## Example: Custom Error Handler @@ -187,7 +185,7 @@ error handler. "fmt" "net/http" - "github.com/johnwarden/httperror/v2" + "github.com/johnwarden/httperror" ) func Example_customErrorHandler() { @@ -247,7 +245,7 @@ such as the status code for successful requests. "fmt" "net/http" - "github.com/johnwarden/httperror/v2" + "github.com/johnwarden/httperror" ) @@ -284,9 +282,9 @@ such as the status code for successful requests. ## Example: Standard Middleware -Because [httperror.Handler](https://pkg.go.dev/github.com/johnwarden/httperror/v2#Handler) implements the standard [http.Handler](https://pkg.go.dev/net/http#Handler) interface, you can apply any of the many middleware created by the Go community for standard http Handlers, as illustrated in the example below. +Because [httperror.Handler](https://pkg.go.dev/github.com/johnwarden/httperror#Handler) implements the standard [http.Handler](https://pkg.go.dev/net/http#Handler) interface, you can apply any of the many middleware created by the Go community for standard http Handlers, as illustrated in the example below. -Note however, the resulting handlers after wrapping will be [http.Handler](https://pkg.go.dev/net/http#Handler)s, and will therefore not be able to return an error or accept additional parameters. The [httprouter example](#example-httprouter) shows hows to use [ApplyStandardMiddleware](https://pkg.go.dev/github.com/johnwarden/httperror/v2#ApplyStandardMiddleware) to apply standard middleware to [httperror.Handler](https://pkg.go.dev/github.com/johnwarden/httperror/v2#Handler)s without changing their signature. +Note however, the resulting handlers after wrapping will be [http.Handler](https://pkg.go.dev/net/http#Handler)s, and will therefore not be able to return an error or accept additional parameters. The [httprouter example](#example-httprouter) shows hows to use [ApplyStandardMiddleware](https://pkg.go.dev/github.com/johnwarden/httperror#ApplyStandardMiddleware) to apply standard middleware to [httperror.Handler](https://pkg.go.dev/github.com/johnwarden/httperror#Handler)s without changing their signature. package httperror_test @@ -295,7 +293,7 @@ Note however, the resulting handlers after wrapping will be [http.Handler](http "net/http" "os" - "github.com/johnwarden/httperror/v2" + "github.com/johnwarden/httperror" gorilla "github.com/gorilla/handlers" ) @@ -317,7 +315,7 @@ Note however, the resulting handlers after wrapping will be [http.Handler](http This example illustrates the use of the error-returning paradigm described in this document with a popular router package, [github.com/julienschmidt/httprouter](https://github.com/julienschmidt/httprouter). To make things more interesting, the handler function accepts its parameters as a struct instead of a value of type [httprouter.Params](https://pkg.go.dev/github.com/julienschmidt/httprouter#Params), thereby decoupling the handler from the router. -Further, we illustrate the use of [ApplyStandardMiddleware](https://pkg.go.dev/github.com/johnwarden/httperror/v2#ApplyStandardMiddleware) to wrap our handler +Further, we illustrate the use of [ApplyStandardMiddleware](https://pkg.go.dev/github.com/johnwarden/httperror#ApplyStandardMiddleware) to wrap our handler with middleware written for a standard [http.Handler](https://pkg.go.dev/net/http#Handler), but still allow our third parameter to be passed in by the router. @@ -325,7 +323,7 @@ with middleware written for a standard [http.Handler](https://pkg.go.dev/net/htt "fmt" "net/http" - "github.com/johnwarden/httperror/v2" + "github.com/johnwarden/httperror" "github.com/julienschmidt/httprouter" "github.com/NYTimes/gziphandler" ) @@ -385,3 +383,70 @@ with middleware written for a standard [http.Handler](https://pkg.go.dev/net/htt +## Example: Panic Middleware + +This example shows how to use [httperror.PanicMiddleware] +(https://pkg.go.dev/github.com/johnwarden/httperror#PanicMiddleware) to serve +an appropriate error page to the user on panic and then trigger a clean HTTP +server shutdown. + + import ( + "context" + "errors" + "fmt" + "log" + "net/http" + "time" + + "github.com/johnwarden/httperror" + ) + + + const maxShutDownTimeout = 5 * time.Second + + func main() { + httpServer := &http.Server{ + Addr: ":8080", + } + + shutdown := func() { + // shut down the HTTP server with a timeout in case the server doesn't want to shut down. + // or waiting for connections to change to idle status takes too long. + ctxWithTimeout, cancel := context.WithTimeout(context.Background(), maxShutDownTimeout) + defer cancel() + err := httpServer.Shutdown(ctxWithTimeout) + if err != nil { + // if server doesn't respond to shutdown signal, nothing remains but to panic. + log.Panic(err) + } + } + + errorHandler := func(w http.ResponseWriter, err error) { + if errors.Is(err, httperror.Panic) { + // the shutdown function must be called in a goroutine. Otherwise, if it is used + // to shutdown the server, we'll get a deadlock with the server shutdown function + // waiting for this request handler to finish, and this request waiting for the + // server shutdown function. + fmt.Println("Shutting down") + go shutdown() + } + + // Now make sure we serve the user an appropriate error page. + httperror.DefaultErrorHandler(w, err) + } + + // This middleware converts panics to errors. + h := httperror.PanicMiddleware(getMeOuttaHere) + + // This is the same helloHandler as the introduction. Add a custom error handler. + httpServer.Handler = httperror.WrapHandlerFunc(h, errorHandler) + + err := httpServer.ListenAndServe() + } + + var getMeOuttaHere = httperror.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error { + w.Header().Set("Content-Type", "text/plain") + panic("Get me outta here!") + return nil + }) + diff --git a/error_test.go b/error_test.go index d62f059..5b04492 100644 --- a/error_test.go +++ b/error_test.go @@ -6,7 +6,7 @@ import ( "github.com/pkg/errors" - "github.com/johnwarden/httperror/v2" + "github.com/johnwarden/httperror" "github.com/stretchr/testify/assert" ) diff --git a/go.mod b/go.mod index b1bc30e..247ed6e 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/johnwarden/httperror/v2 +module github.com/johnwarden/httperror go 1.19 diff --git a/httperror.go b/httperror.go index 26015be..21b1c9e 100644 --- a/httperror.go +++ b/httperror.go @@ -1,5 +1,5 @@ /* -Package httperror is for writing HTTP handlers that return errors instead of handling them directly. See the documentation at https://github.com/johnwarden/httperror/v2 +Package httperror is for writing HTTP handlers that return errors instead of handling them directly. See the documentation at https://github.com/johnwarden/httperror */ package httperror diff --git a/panic.go b/panic.go index fbd50e1..e6d9bb0 100644 --- a/panic.go +++ b/panic.go @@ -1,29 +1,48 @@ package httperror import ( + "errors" "fmt" "net/http" ) +var Panic = panicError{} + +type panicError struct { + innerError error + message string +} + +func (e panicError) Error() string { + if e.innerError != nil { + return "panic: " + e.innerError.Error() + } + return "panic: " + e.message +} + +func (e panicError) Unwrap() error { + return e.innerError +} + +func (e panicError) Is(other error) bool { + if other == Panic { + return true + } + return errors.Is(e.innerError, other) +} + // PanicMiddleware wraps a [httperror.Handler], returning a new [httperror.HandlerFunc] that -// recovers from panics and returns them as errors. The second argument is an optional -// function that is called if there is a panic. This function can be used, for example, to -// cleanly shutdown the server. -func PanicMiddleware(h Handler, s func(error)) HandlerFunc { +// recovers from panics and returns them as errors. Panic error can be identified using +// errors.Is(err, httperror.Panic) +func PanicMiddleware(h Handler) HandlerFunc { return func(w http.ResponseWriter, r *http.Request) (err error) { - defer func() { if r := recover(); r != nil { isErr := false if err, isErr = r.(error); !isErr { - err = fmt.Errorf("%v", r) - } - if s != nil { - // the shutdown function must be called in a goroutine. Otherwise, if it is used - // to shutdown the server, we'll get a deadlock with the server shutdown function - // waiting for this request handler to finish, and this request waiting for the - // server shutdown function. - go s(err) + err = panicError{nil, fmt.Sprintf("%v", r)} + } else { + err = panicError{err, ""} } } }() @@ -34,20 +53,17 @@ func PanicMiddleware(h Handler, s func(error)) HandlerFunc { } // XPanicMiddleware wraps a [httperror.XHandler], returning a new [httperror.XHandlerFunc] that -// recovers from panics and returns them as errors. The second argument is an optional -// function that is called if there is a panic. This function can be used, for example, to -// cleanly shutdown the server. -func XPanicMiddleware[P any](h XHandler[P], s func(error)) XHandlerFunc[P] { +// recovers from panics and returns them as errors. Panic error can be identified using +// errors.Is(err, httperror.Panic) +func XPanicMiddleware[P any](h XHandler[P]) XHandlerFunc[P] { return func(w http.ResponseWriter, r *http.Request, p P) (err error) { defer func() { if r := recover(); r != nil { isErr := false if err, isErr = r.(error); !isErr { - err = fmt.Errorf("%v", r) - } - if s != nil { - // the shutdown function must be called in a goroutine. See comment in PanicMiddleware above. - go s(err) + err = panicError{nil, fmt.Sprintf("%v", r)} + } else { + err = panicError{err, ""} } } }() diff --git a/request_test.go b/request_test.go index e81f299..72788d0 100644 --- a/request_test.go +++ b/request_test.go @@ -4,14 +4,14 @@ import ( "bytes" "errors" "fmt" - "io/ioutil" + "io" "net/http" "net/http/httptest" "net/url" "strings" "testing" - "github.com/johnwarden/httperror/v2" + "github.com/johnwarden/httperror" "github.com/stretchr/testify/assert" ) @@ -24,7 +24,7 @@ func testRequest(h http.Handler, path string) (int, string) { resp := rr.Result() defer resp.Body.Close() // io.Copy(os.Stdout, res.Body) - body, _ := ioutil.ReadAll(resp.Body) + body, _ := io.ReadAll(resp.Body) return resp.StatusCode, string(body) } @@ -49,16 +49,41 @@ func TestCustomErrorHandler(t *testing.T) { } func TestPanic(t *testing.T) { - h := getMeOuttaHere - errChan := make(chan error) - h = httperror.PanicMiddleware(h, func(e error) {errChan <- e}) - s, m := testRequest(h, "/") - assert.Equal(t, 500, s, "got 500 status code") - assert.Equal(t, "500 Internal Server Error\n", m, "got 500 text/plain response") + { + h := getMeOuttaHere + h = httperror.PanicMiddleware(h) + + var e error + errorHandler := func(w http.ResponseWriter, err error) { + e = err + httperror.DefaultErrorHandler(w, err) + } + + s, m := testRequest(httperror.WrapHandlerFunc(h, errorHandler), "/") + assert.Equal(t, 500, s, "got 500 status code") + assert.Equal(t, "500 Internal Server Error\n", m, "got 500 text/plain response") + assert.True(t, errors.Is(e, httperror.Panic)) + assert.Equal(t, "panic: Get me outta here!", e.Error()) + } - e := <-errChan + { + h := fail + h = httperror.PanicMiddleware(h) + + var e error + errorHandler := func(w http.ResponseWriter, err error) { + e = err + httperror.DefaultErrorHandler(w, err) + } + + s, m := testRequest(httperror.WrapHandlerFunc(h, errorHandler), "/") + assert.Equal(t, 500, s, "got 500 status code") + assert.Equal(t, "500 Internal Server Error\n", m, "got 500 text/plain response") + assert.True(t, errors.Is(e, httperror.Panic)) + assert.True(t, errors.Is(e, sentinalError)) + assert.Equal(t, "panic: SOME_ERROR", e.Error()) - assert.Equal(t, "Get me outta here!", e.Error()) + } } func TestApplyStandardMiddleware(t *testing.T) { @@ -86,13 +111,18 @@ func TestApplyStandardMiddleware(t *testing.T) { assert.Equal(t, 200, s) assert.Equal(t, "Hello, Bill\n", m, "got middleware output") } - } +var sentinalError = fmt.Errorf("SOME_ERROR") + var getMeOuttaHere = httperror.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error { w.Header().Set("Content-Type", "text/plain") panic("Get me outta here!") - return nil +}) + +var fail = httperror.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error { + w.Header().Set("Content-Type", "text/plain") + panic(sentinalError) }) var okHandler = httperror.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error { diff --git a/standardmiddleware.go b/standardmiddleware.go index 1143e19..a0cc429 100644 --- a/standardmiddleware.go +++ b/standardmiddleware.go @@ -7,9 +7,7 @@ import ( type contextKey string -var ( - key = contextKey("key") -) +var key = contextKey("key") // StandardMiddleware is a standard http.Handler wrapper. type StandardMiddleware = func(http.Handler) http.Handler @@ -50,6 +48,7 @@ func XApplyStandardMiddleware[P any](h XHandler[P], ms ...StandardMiddleware) XH return sm.err } } + // ApplyStandardMiddleware applies middleware written for a standard // [http.Handler] to an [httperror.Handler], returning an // [httperror.Handler]. It is possible to apply standard middleware to @@ -60,7 +59,6 @@ func XApplyStandardMiddleware[P any](h XHandler[P], ms ...StandardMiddleware) XH // could not return an error. This function solves that problem by passing // errors and parameters through the context. func ApplyStandardMiddleware(h Handler, ms ...StandardMiddleware) HandlerFunc { - var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() sm := ctx.Value(key).(*standardMiddleware[any])