aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStefan Majewsky <majewsky@gmx.net>2026-07-29 21:56:06 +0200
committerStefan Majewsky <majewsky@gmx.net>2026-07-29 22:33:37 +0200
commit7ead3465c7fdf564ae367e1169aeb68ced9267c8 (patch)
treed6647e498a9663def6a2bbfd89dba1a59135fc25
parente7c4ea85915d525c738ae0da5494da57926b07ca (diff)
downloadgo-gg-7ead3465c7fdf564ae367e1169aeb68ced9267c8.tar.gz
add package errext
-rw-r--r--CHANGELOG.md2
-rw-r--r--README.md4
-rw-r--r--errext/cleanup.go75
-rw-r--r--errext/cleanup_test.go41
-rw-r--r--errext/errext.go5
5 files changed, 126 insertions, 1 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a253288..2565fa6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,7 +7,7 @@ SPDX-License-Identifier: Apache-2.0
Changes:
-- Add package gsql.
+- Add packages errext and gsql.
# v1.11.1 (2026-06-27)
diff --git a/README.md b/README.md
index 5e2b2a4..e4f404f 100644
--- a/README.md
+++ b/README.md
@@ -20,6 +20,10 @@ My personal extension of the standard library.
- [gsql](./gsql/): abstraction layer for database libraries, supporting both database/sql drivers and non-standard drivers like [pgx](https://github.com/jackc/pgx)
+### Addons for errors
+
+- [errext](./errext/): composite error types
+
### Addons for net/http
- [assetembed](./assetembed/): HTTP handler for efficiently serving embedded assets using the cache-busting pattern
diff --git a/errext/cleanup.go b/errext/cleanup.go
new file mode 100644
index 0000000..908f93a
--- /dev/null
+++ b/errext/cleanup.go
@@ -0,0 +1,75 @@
+// SPDX-FileCopyrightText: 2026 Stefan Majewsky <majewsky@gmx.net>
+// SPDX-License-Identifier: Apache-2.0
+
+package errext
+
+import "fmt"
+
+// WithCleanup combines two errors into a single error:
+// - (optionally) a main error from an IO operation (e.g. a directory listing or a database read)
+// - an auxiliary error from closing or otherwise cleaning up the respective IO handle
+//
+// It is intended to simplify situations where a Close() or Commit()/Rollback() call is required at the end of the operation,
+// but its error should not be swallowed if there is one.
+// For example:
+//
+// db, err := sql.Open("postgres", dbURL)
+// if err != nil {
+// return nil, err
+// }
+// err = applySchema(db)
+// if err != nil {
+// err2 := db.Close()
+// if err2 == nil {
+// return nil, err
+// } else {
+// return nil, fmt.Errorf("%w (additional error during db.Close(): %s)", err, err2.Error())
+// }
+// }
+// return db, nil
+//
+// can be shortened to:
+//
+// db, err := sql.Open("postgres", dbURL)
+// if err != nil {
+// return nil, err
+// }
+// err = applySchema(db)
+// if err != nil {
+// return errext.WithCleanup(err, "db.Close", db.Close())
+// }
+// return db, nil
+//
+// If cleanupErr is nil, err is returned unchanged.
+//
+// If err is nil, but cleanupErr is not nil, cleanupErr will be wrapped to indicate the name of the cleanup operation that failed.
+func WithCleanup(err error, cleanupOperation string, cleanupErr error) error {
+ if cleanupErr == nil {
+ return err
+ }
+ return cleanupError{err, cleanupErr, cleanupOperation}
+}
+
+type cleanupError struct {
+ MainError error
+ CleanupError error
+ CleanupOperation string
+}
+
+// Error implements the builtin/error interface.
+func (e cleanupError) Error() string {
+ if e.MainError == nil {
+ return fmt.Sprintf("during %s(): %s", e.CleanupOperation, e.CleanupError.Error())
+ } else {
+ return fmt.Sprintf("%s (additional error during %s(): %s)", e.MainError.Error(), e.CleanupOperation, e.CleanupError.Error())
+ }
+}
+
+// Unwrap implements the interface implied by the documentation of package errors.
+func (e cleanupError) Unwrap() []error {
+ if e.MainError == nil {
+ return []error{e.CleanupError}
+ } else {
+ return []error{e.MainError, e.CleanupError}
+ }
+}
diff --git a/errext/cleanup_test.go b/errext/cleanup_test.go
new file mode 100644
index 0000000..09b3173
--- /dev/null
+++ b/errext/cleanup_test.go
@@ -0,0 +1,41 @@
+// SPDX-FileCopyrightText: 2026 Stefan Majewsky <majewsky@gmx.net>
+// SPDX-License-Identifier: Apache-2.0
+
+package errext_test
+
+import (
+ "errors"
+ "testing"
+
+ "go.xyrillian.de/gg/assert"
+ "go.xyrillian.de/gg/errext"
+)
+
+type fooError struct{}
+type barError struct{}
+type bazError struct{}
+
+func (fooError) Error() string { return "foo" }
+func (barError) Error() string { return "bar" }
+func (bazError) Error() string { return "baz" }
+
+func TestIOError(t *testing.T) {
+ err := errext.WithCleanup(nil, "File.Close", nil)
+ assert.Equal(t, err == nil, true)
+
+ err = errext.WithCleanup(fooError{}, "File.Close", nil)
+ assert.ErrEqual(t, err, "foo")
+ assert.Equal(t, err, error(fooError{})) // check for no wrapping in type ioError without cleanup error
+
+ err = errext.WithCleanup(nil, "File.Close", barError{})
+ assert.ErrEqual(t, err, "during File.Close(): bar")
+ assert.Equal(t, errors.Is(err, fooError{}), false)
+ assert.Equal(t, errors.Is(err, barError{}), true)
+ assert.Equal(t, errors.Is(err, bazError{}), false)
+
+ err = errext.WithCleanup(fooError{}, "File.Close", barError{})
+ assert.ErrEqual(t, err, "foo (additional error during File.Close(): bar)")
+ assert.Equal(t, errors.Is(err, fooError{}), true)
+ assert.Equal(t, errors.Is(err, barError{}), true)
+ assert.Equal(t, errors.Is(err, bazError{}), false)
+}
diff --git a/errext/errext.go b/errext/errext.go
new file mode 100644
index 0000000..4d8d761
--- /dev/null
+++ b/errext/errext.go
@@ -0,0 +1,5 @@
+// SPDX-FileCopyrightText: 2026 Stefan Majewsky <majewsky@gmx.net>
+// SPDX-License-Identifier: Apache-2.0
+
+// Package errext contains versatile composite error types.
+package errext