From ef8261752f73f0901082dbd3c433b7e90cfa7fd1 Mon Sep 17 00:00:00 2001 From: "Gustavo \"Guz\" L de Mello" Date: Tue, 25 Feb 2025 14:30:14 -0300 Subject: [PATCH] feat(smalltrip,exceptions): use a JSON handler if none is provided via the context --- smalltrip/exceptions/exceptions.go | 7 +++ smalltrip/exceptions/middleware.go | 71 ++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 smalltrip/exceptions/middleware.go diff --git a/smalltrip/exceptions/exceptions.go b/smalltrip/exceptions/exceptions.go index 3e79943..f8b396d 100644 --- a/smalltrip/exceptions/exceptions.go +++ b/smalltrip/exceptions/exceptions.go @@ -12,6 +12,11 @@ type Exception struct { Message string `json:"message"` // User friendly message Err error `json:"error,omitempty"` // Go error Severity Severity `json:"severity"` // Exception level + + // Handler to be used. This is normally provided by a middleware via the + // request context. Setting this field overrides any provided by the middleware + // and can be used to add a handler when using a middleware is not possible. + handler HandlerFunc `json:"-"` } var ( @@ -33,6 +38,8 @@ func (e Exception) ServeHTTP(w http.ResponseWriter, r *http.Request) { e.handler(e, w, r) } + e.handler = HandlerJSON(HandlerText) + handler, ok := r.Context().Value(handlerFuncCtxKey).(HandlerFunc) if !ok { e.handler(e, w, r) diff --git a/smalltrip/exceptions/middleware.go b/smalltrip/exceptions/middleware.go new file mode 100644 index 0000000..7d487a6 --- /dev/null +++ b/smalltrip/exceptions/middleware.go @@ -0,0 +1,71 @@ +package exceptions + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" +) + +func HandlerJSON(fallback HandlerFunc) HandlerFunc { + return func(e Exception, w http.ResponseWriter, r *http.Request) { + j, err := json.Marshal(e) + if err != nil { + e.Err = errors.Join(fmt.Errorf("marshalling Exception struct: %s", e.Error()), e.Err) + + fallback(e, w, r) + return + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(e.Status) + + _, err = w.Write(j) + if err != nil { + e.Err = errors.Join(fmt.Errorf("writing JSON response to body: %s", e.Error()), e.Err) + + HandlerText(e, w, r) + return + } + } +} + +var _ HandlerFunc = HandlerJSON(HandlerText) + +func HandlerText(e Exception, w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(e.Status) + + _, err := w.Write([]byte(fmt.Sprintf( + "Status: %3d\n"+ + "Code: %s"+ + "Message: %s\n"+ + "Err: %s\n"+ + "Severity: %s\n\n"+ + "%+v\n\n"+ + "%#v", + e.Status, + e.Code, + e.Message, + e.Err, + e.Severity, + + e, e, + ))) + if err != nil { + _, _ = w.Write([]byte(fmt.Sprintf( + "Ok, what should we do at this point? You fucked up so bad that this message " + + "shouldn't even be able to be sent in the first place. If you are a normal user I'm " + + "so sorry for you to be reading this. If you're a developer, go fix your ResponseWriter " + + "implementation, because this should never happen in any normal codebase. " + + "I hope for the life of anyone you love you don't use this message in some " + + "error checking or any sort of API-contract, because there will be no more hope " + + "for you or your project. May God or any other or any other divinity that you may " + + "or may not believe be with you when trying to fix this mistake, you will need it.", + // If someone use this as part of the API-contract I'll not even be surprised. + // So any change to this message is still considered a breaking change. + ))) + } +} + +var _ HandlerFunc = HandlerText