summaryrefslogtreecommitdiff
path: root/internal/gtserror
diff options
context:
space:
mode:
Diffstat (limited to 'internal/gtserror')
-rw-r--r--internal/gtserror/new.go58
-rw-r--r--internal/gtserror/new_caller.go92
-rw-r--r--internal/gtserror/new_nocaller.go38
-rw-r--r--internal/gtserror/new_test.go11
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)
}