diff options
| author | Stefan Majewsky <majewsky@gmx.net> | 2026-07-29 21:56:06 +0200 |
|---|---|---|
| committer | Stefan Majewsky <majewsky@gmx.net> | 2026-07-29 22:33:37 +0200 |
| commit | 7ead3465c7fdf564ae367e1169aeb68ced9267c8 (patch) | |
| tree | d6647e498a9663def6a2bbfd89dba1a59135fc25 | |
| parent | e7c4ea85915d525c738ae0da5494da57926b07ca (diff) | |
| download | go-gg-7ead3465c7fdf564ae367e1169aeb68ced9267c8.tar.gz | |
add package errext
| -rw-r--r-- | CHANGELOG.md | 2 | ||||
| -rw-r--r-- | README.md | 4 | ||||
| -rw-r--r-- | errext/cleanup.go | 75 | ||||
| -rw-r--r-- | errext/cleanup_test.go | 41 | ||||
| -rw-r--r-- | errext/errext.go | 5 |
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) @@ -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 |
