diff options
Diffstat (limited to 'internal/gtserror')
-rw-r--r-- | internal/gtserror/new.go | 58 | ||||
-rw-r--r-- | internal/gtserror/new_caller.go | 92 | ||||
-rw-r--r-- | internal/gtserror/new_nocaller.go | 38 | ||||
-rw-r--r-- | internal/gtserror/new_test.go | 11 |
4 files changed, 163 insertions, 36 deletions
diff --git a/internal/gtserror/new.go b/internal/gtserror/new.go index ad20e5cac..bb88d5f6a 100644 --- a/internal/gtserror/new.go +++ b/internal/gtserror/new.go @@ -18,48 +18,38 @@ package gtserror import ( - "errors" "net/http" - - "codeberg.org/gruf/go-byteutil" ) -// NewResponseError crafts an error from provided HTTP response -// including the method, status and body (if any provided). This -// will also wrap the returned error using WithStatusCode(). -func NewResponseError(rsp *http.Response) error { - var buf byteutil.Buffer - - // Get URL string ahead of time. - urlStr := rsp.Request.URL.String() +// New returns a new error, prepended with caller function name if gtserror.Caller is enabled. +func New(msg string) error { + return newAt(3, msg) +} - // Alloc guesstimate of required buf size. - buf.Guarantee(0 + - len(rsp.Request.Method) + - 12 + // request to - len(urlStr) + - 17 + // failed: status=" - len(rsp.Status) + - 8 + // " body=" - 256 + // max body size - 1, // " - ) +// Newf returns a new formatted error, prepended with caller function name if gtserror.Caller is enabled. +func Newf(msgf string, args ...any) error { + return newfAt(3, msgf, args...) +} - // Build error message string without +// NewResponseError crafts an error from provided HTTP response +// including the method, status and body (if any provided). This +// will also wrap the returned error using WithStatusCode() and +// will include the caller function name as a prefix. +func NewFromResponse(rsp *http.Response) error { + // Build error with message without // using "fmt", as chances are this will // be used in a hot code path and we // know all the incoming types involved. - _, _ = buf.WriteString(rsp.Request.Method) - _, _ = buf.WriteString(" request to ") - _, _ = buf.WriteString(urlStr) - _, _ = buf.WriteString(" failed: status=\"") - _, _ = buf.WriteString(rsp.Status) - _, _ = buf.WriteString("\" body=\"") - _, _ = buf.WriteString(drainBody(rsp.Body, 256)) - _, _ = buf.WriteString("\"") - - // Create new error from msg. - err := errors.New(buf.String()) + err := newAt(3, ""+ + rsp.Request.Method+ + " request to "+ + rsp.Request.URL.String()+ + " failed: status=\""+ + rsp.Status+ + "\" body=\""+ + drainBody(rsp.Body, 256)+ + "\"", + ) // Wrap error to provide status code. return WithStatusCode(err, rsp.StatusCode) diff --git a/internal/gtserror/new_caller.go b/internal/gtserror/new_caller.go new file mode 100644 index 000000000..46ae76d6a --- /dev/null +++ b/internal/gtserror/new_caller.go @@ -0,0 +1,92 @@ +// GoToSocial +// Copyright (C) GoToSocial Authors admin@gotosocial.org +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + +//go:build !noerrcaller + +package gtserror + +import ( + "errors" + "fmt" + "runtime" + "strings" +) + +// Caller returns whether created errors will prepend calling function name. +const Caller = true + +// cerror wraps an error with a string +// prefix of the caller function name. +type cerror struct { + c string + e error +} + +func (ce *cerror) Error() string { + msg := ce.e.Error() + return ce.c + ": " + msg +} + +func (ce *cerror) Unwrap() error { + return ce.e +} + +// newAt is the same as New() but allows specifying calldepth. +func newAt(calldepth int, msg string) error { + return &cerror{ + c: caller(calldepth + 1), + e: errors.New(msg), + } +} + +// newfAt is the same as Newf() but allows specifying calldepth. +func newfAt(calldepth int, msgf string, args ...any) error { + return &cerror{ + c: caller(calldepth + 1), + e: fmt.Errorf(msgf, args...), + } +} + +// caller fetches the calling function name, skipping 'depth'. Results are cached per PC. +func caller(depth int) string { + var pcs [1]uintptr + + // Fetch calling function using calldepth + _ = runtime.Callers(depth, pcs[:]) + fn := runtime.FuncForPC(pcs[0]) + + if fn == nil { + return "" + } + + // Get func name. + name := fn.Name() + + // Drop everything but but function name itself + if idx := strings.LastIndexByte(name, '.'); idx >= 0 { + name = name[idx+1:] + } + + const params = `[...]` + + // Drop any generic type parameter markers + if idx := strings.Index(name, params); idx >= 0 { + name = name[:idx] + name[idx+len(params):] + } + + return name +} diff --git a/internal/gtserror/new_nocaller.go b/internal/gtserror/new_nocaller.go new file mode 100644 index 000000000..e4eeb5ec9 --- /dev/null +++ b/internal/gtserror/new_nocaller.go @@ -0,0 +1,38 @@ +// GoToSocial +// Copyright (C) GoToSocial Authors admin@gotosocial.org +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + +//go:build noerrcaller + +package gtserror + +import ( + "errors" + "fmt" +) + +// Caller returns whether created errors will prepend calling function name. +const Caller = false + +// newAt is the same as New() but allows specifying calldepth. +func newAt(_ int, msg string) error { + return errors.New(msg) +} + +// newfAt is the same as Newf() but allows specifying calldepth. +func newfAt(_ int, msgf string, args ...any) error { + return fmt.Errorf(msgf, args...) +} diff --git a/internal/gtserror/new_test.go b/internal/gtserror/new_test.go index b0824b5a7..8b4dae1ba 100644 --- a/internal/gtserror/new_test.go +++ b/internal/gtserror/new_test.go @@ -10,6 +10,7 @@ import ( "testing" "github.com/superseriousbusiness/gotosocial/internal/gtserror" + "github.com/superseriousbusiness/gotosocial/internal/log" ) func TestResponseError(t *testing.T) { @@ -53,13 +54,19 @@ func testResponseError(t *testing.T, rsp http.Response) { body = string(b[:trunc]) } expect := fmt.Sprintf( - "%s request to %s failed: status=\"%s\" body=\"%s\"", + "%s%s request to %s failed: status=\"%s\" body=\"%s\"", + func() string { + if gtserror.Caller { + return strings.Split(log.Caller(3), ".")[1] + ": " + } + return "" + }(), rsp.Request.Method, rsp.Request.URL.String(), rsp.Status, body, ) - err := gtserror.NewResponseError(&rsp) + err := gtserror.NewFromResponse(&rsp) if str := err.Error(); str != expect { t.Errorf("unexpected error string: recv=%q expct=%q", str, expect) } |