wrap errors

Implement error wrapping so we catch all errors into a single error.
This commit is contained in:
brent s. 2022-01-09 15:39:37 -05:00
parent b4419a6f8c
commit d13b263222
Signed by: bts
GPG Key ID: 8C004C2F93481F6B
9 changed files with 86 additions and 107 deletions

View File

@ -224,6 +224,8 @@ func main() {
} }
---- ----


Note that many functions/methods may return a https://pkg.go.dev/r00t2.io/goutils/multierr#MultiError[`(r00t2.io/goutils/)multierr.MultiError`^], which you may attempt to typeswitch to receive the original errors in their native error format. The functions/methods which may return a MultiError are noted as such in their individual documentation.

== Library Hacking == Library Hacking


=== Reference === Reference

View File

@ -4,6 +4,7 @@ import (
"time" "time"


"github.com/godbus/dbus/v5" "github.com/godbus/dbus/v5"
"r00t2.io/goutils/multierr"
) )


/* /*
@ -146,13 +147,16 @@ func (c *Collection) Delete() (err error) {
return return
} }


// Items returns a slice of Item pointers in the Collection. /*
Items returns a slice of Item pointers in the Collection.
err MAY be a *multierr.MultiError.
*/
func (c *Collection) Items() (items []*Item, err error) { func (c *Collection) Items() (items []*Item, err error) {


var paths []dbus.ObjectPath var paths []dbus.ObjectPath
var item *Item var item *Item
var variant dbus.Variant var variant dbus.Variant
var errs []error = make([]error, 0) var errs *multierr.MultiError = multierr.NewMultiError()


if variant, err = c.Dbus.GetProperty(DbusCollectionItems); err != nil { if variant, err = c.Dbus.GetProperty(DbusCollectionItems); err != nil {
return return
@ -165,13 +169,16 @@ func (c *Collection) Items() (items []*Item, err error) {
for _, path := range paths { for _, path := range paths {
item = nil item = nil
if item, err = NewItem(c, path); err != nil { if item, err = NewItem(c, path); err != nil {
errs = append(errs, err) errs.AddError(err)
err = nil err = nil
continue continue
} }
items = append(items, item) items = append(items, item)
} }
err = NewErrors(err)
if !errs.IsEmpty() {
err = errs
}


return return
} }
@ -248,18 +255,20 @@ func (c *Collection) Relabel(newLabel string) (err error) {
} }


/* /*
SearchItems searches a Collection for a matching profile string. SearchItems searches a Collection for a matching "profile" string.
It's mostly a carry-over from go-libsecret, and is here for convenience. IT MAY BE REMOVED IN THE FUTURE. It's mostly a carry-over from go-libsecret, and is here for convenience. IT MAY BE REMOVED IN THE FUTURE.


I promise it's not useful for any other implementation/storage of SecretService whatsoever. I promise it's not useful for any other implementation/storage of SecretService whatsoever.


err MAY be a *multierr.MultiError.

Deprecated: Use Service.SearchItems instead. Deprecated: Use Service.SearchItems instead.
*/ */
func (c *Collection) SearchItems(profile string) (items []*Item, err error) { func (c *Collection) SearchItems(profile string) (items []*Item, err error) {


var call *dbus.Call var call *dbus.Call
var paths []dbus.ObjectPath var paths []dbus.ObjectPath
var errs []error = make([]error, 0) var errs *multierr.MultiError = multierr.NewMultiError()
var attrs map[string]string = make(map[string]string, 0) var attrs map[string]string = make(map[string]string, 0)
var item *Item var item *Item


@ -280,13 +289,16 @@ func (c *Collection) SearchItems(profile string) (items []*Item, err error) {
for _, path := range paths { for _, path := range paths {
item = nil item = nil
if item, err = NewItem(c, path); err != nil { if item, err = NewItem(c, path); err != nil {
errs = append(errs, err) errs.AddError(err)
err = nil err = nil
continue continue
} }
items = append(items, item) items = append(items, item)
} }
err = NewErrors(err)
if !errs.IsEmpty() {
err = errs
}


return return
} }

4
doc.go
View File

@ -84,5 +84,9 @@ Usage


Full documentation can be found via inline documentation. Full documentation can be found via inline documentation.
Additionally, use either https://pkg.go.dev/r00t2.io/gosecret or https://pkg.go.dev/golang.org/x/tools/cmd/godoc (or `go doc`) in the source root. Additionally, use either https://pkg.go.dev/r00t2.io/gosecret or https://pkg.go.dev/golang.org/x/tools/cmd/godoc (or `go doc`) in the source root.

Note that many functions/methods may return a (r00t2.io/goutils/)multierr.MultiError (https://pkg.go.dev/r00t2.io/goutils/multierr#MultiError),
which you may attempt to typeswitch back to a *multierr.MultiErr to receive the original errors in their native error format (MultiError.Errors).
The functions/methods which may return a MultiError are noted as such in their individual documentation.
*/ */
package gosecret package gosecret

View File

@ -4,6 +4,7 @@ import (
`strings` `strings`


`github.com/godbus/dbus/v5` `github.com/godbus/dbus/v5`
`r00t2.io/goutils/multierr`
) )


// isPrompt returns a boolean that is true if path is/requires a prompt(ed path) and false if it is/does not. // isPrompt returns a boolean that is true if path is/requires a prompt(ed path) and false if it is/does not.
@ -63,19 +64,27 @@ func pathIsValid(path interface{}) (ok bool, err error) {


/* /*
validConnPath condenses the checks for connIsValid and pathIsValid into one func due to how frequently this check is done. validConnPath condenses the checks for connIsValid and pathIsValid into one func due to how frequently this check is done.
err is a MultiError, which can be treated as an error.error. (See https://pkg.go.dev/builtin#error)
If err is not nil, it IS a *multierr.MultiError.
*/ */
func validConnPath(conn *dbus.Conn, path interface{}) (cr *ConnPathCheckResult, err error) { func validConnPath(conn *dbus.Conn, path interface{}) (cr *ConnPathCheckResult, err error) {


var connErr error var errs *multierr.MultiError = multierr.NewMultiError()
var pathErr error


cr = new(ConnPathCheckResult) cr = new(ConnPathCheckResult)


cr.ConnOK, connErr = connIsValid(conn) if cr.ConnOK, err = connIsValid(conn); err != nil {
cr.PathOK, pathErr = pathIsValid(path) errs.AddError(err)
err = nil
}
if cr.PathOK, err = pathIsValid(path); err != nil {
errs.AddError(err)
err = nil
}


err = NewErrors(connErr, pathErr) if !errs.IsEmpty() {
err = errs
}


return return
} }

1
go.mod
View File

@ -5,4 +5,5 @@ go 1.17
require ( require (
github.com/godbus/dbus/v5 v5.0.6 github.com/godbus/dbus/v5 v5.0.6
github.com/google/uuid v1.3.0 github.com/google/uuid v1.3.0
r00t2.io/goutils v1.1.2
) )

5
go.sum
View File

@ -1,4 +1,9 @@
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/godbus/dbus/v5 v5.0.6 h1:mkgN1ofwASrYnJ5W6U/BxG15eXXXjirgZc7CLqkcaro= github.com/godbus/dbus/v5 v5.0.6 h1:mkgN1ofwASrYnJ5W6U/BxG15eXXXjirgZc7CLqkcaro=
github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
r00t2.io/goutils v1.1.2 h1:zOOqNHQ/HpJVggV5NTXBcd7FQtBP2C/sMLkHw3YvBzU=
r00t2.io/goutils v1.1.2/go.mod h1:9ObJI9S71wDLTOahwoOPs19DhZVYrOh4LEHmQ8SW4Lk=
r00t2.io/sysutils v1.1.1/go.mod h1:Wlfi1rrJpoKBOjWiYM9rw2FaiZqraD6VpXyiHgoDo/o=

View File

@ -1,58 +0,0 @@
package gosecret

import (
"fmt"
)

/*
NewErrors returns a new MultiError based on a slice of error.Error (errs).
Any nil errors are trimmed. If there are no actual errors after trimming, err will be nil.
*/
func NewErrors(errs ...error) (err error) {

if errs == nil || len(errs) == 0 {
return
}

var realErrs []error = make([]error, 0)

for _, e := range errs {
if e == nil {
continue
}
realErrs = append(realErrs, e)
}

if len(realErrs) == 0 {
return
}

err = &MultiError{
Errors: realErrs,
ErrorSep: "\n",
}

return
}

// Error makes a MultiError conform to the error interface.
func (e *MultiError) Error() (errStr string) {

var numErrs int

if e == nil || len(e.Errors) == 0 {
return
} else {
numErrs = len(e.Errors)
}

for idx, err := range e.Errors {
if (idx + 1) < numErrs {
errStr += fmt.Sprintf("%v%v", err.Error(), e.ErrorSep)
} else {
errStr += err.Error()
}
}

return
}

View File

@ -8,6 +8,7 @@ import (
"time" "time"


"github.com/godbus/dbus/v5" "github.com/godbus/dbus/v5"
`r00t2.io/goutils/multierr`
) )


// NewService returns a pointer to a new Service connection. // NewService returns a pointer to a new Service connection.
@ -49,13 +50,17 @@ func (s *Service) Close() (err error) {
return return
} }


// Collections returns a slice of Collection items accessible to this Service. /*
Collections returns a slice of Collection items accessible to this Service.

err MAY be a *multierr.MultiError.
*/
func (s *Service) Collections() (collections []*Collection, err error) { func (s *Service) Collections() (collections []*Collection, err error) {


var paths []dbus.ObjectPath var paths []dbus.ObjectPath
var variant dbus.Variant var variant dbus.Variant
var coll *Collection var coll *Collection
var errs []error = make([]error, 0) var errs *multierr.MultiError = multierr.NewMultiError()


if variant, err = s.Dbus.GetProperty(DbusServiceCollections); err != nil { if variant, err = s.Dbus.GetProperty(DbusServiceCollections); err != nil {
return return
@ -68,14 +73,16 @@ func (s *Service) Collections() (collections []*Collection, err error) {
for _, path := range paths { for _, path := range paths {
coll = nil coll = nil
if coll, err = NewCollection(s, path); err != nil { if coll, err = NewCollection(s, path); err != nil {
// return errs.AddError(err)
errs = append(errs, err)
err = nil err = nil
continue continue
} }
collections = append(collections, coll) collections = append(collections, coll)
} }
err = NewErrors(err)
if !errs.IsEmpty() {
err = errs
}


return return
} }
@ -136,10 +143,12 @@ func (s *Service) CreateCollection(label string) (collection *Collection, err er
/* /*
GetCollection returns a single Collection based on the name (name can also be an alias). GetCollection returns a single Collection based on the name (name can also be an alias).
It's a helper function that avoids needing to make multiple calls in user code. It's a helper function that avoids needing to make multiple calls in user code.

err MAY be a *multierr.MultiError.
*/ */
func (s *Service) GetCollection(name string) (c *Collection, err error) { func (s *Service) GetCollection(name string) (c *Collection, err error) {


var errs []error = make([]error, 0) var errs *multierr.MultiError = multierr.NewMultiError()
var colls []*Collection var colls []*Collection
var pathName string var pathName string


@ -160,7 +169,7 @@ func (s *Service) GetCollection(name string) (c *Collection, err error) {
} }
for _, i := range colls { for _, i := range colls {
if pathName, err = NameFromPath(i.Dbus.Path()); err != nil { if pathName, err = NameFromPath(i.Dbus.Path()); err != nil {
errs = append(errs, err) errs.AddError(err)
err = nil err = nil
continue continue
} }
@ -179,9 +188,8 @@ func (s *Service) GetCollection(name string) (c *Collection, err error) {
} }


// Couldn't find it by the given name. // Couldn't find it by the given name.
if errs != nil || len(errs) > 0 { if !errs.IsEmpty() {
errs = append([]error{ErrDoesNotExist}, errs...) err = errs
err = NewErrors(errs...)
} else { } else {
err = ErrDoesNotExist err = ErrDoesNotExist
} }
@ -384,6 +392,8 @@ func (s *Service) RemoveAlias(alias string) (err error) {


/* /*
SearchItems searches all Collection objects and returns all matches based on the map of attributes. SearchItems searches all Collection objects and returns all matches based on the map of attributes.

err MAY be a *multierr.MultiError.
*/ */
func (s *Service) SearchItems(attributes map[string]string) (unlockedItems []*Item, lockedItems []*Item, err error) { func (s *Service) SearchItems(attributes map[string]string) (unlockedItems []*Item, lockedItems []*Item, err error) {


@ -396,7 +406,7 @@ func (s *Service) SearchItems(attributes map[string]string) (unlockedItems []*It
var c *Collection var c *Collection
var cPath dbus.ObjectPath var cPath dbus.ObjectPath
var item *Item var item *Item
var errs []error = make([]error, 0) var errs *multierr.MultiError = multierr.NewMultiError()


if attributes == nil || len(attributes) == 0 { if attributes == nil || len(attributes) == 0 {
err = ErrMissingAttrs err = ErrMissingAttrs
@ -431,16 +441,17 @@ func (s *Service) SearchItems(attributes map[string]string) (unlockedItems []*It
cPath = dbus.ObjectPath(filepath.Dir(string(i))) cPath = dbus.ObjectPath(filepath.Dir(string(i)))


if c, ok = collections[cPath]; !ok { if c, ok = collections[cPath]; !ok {
errs = append(errs, errors.New(fmt.Sprintf( errs.AddError(errors.New(fmt.Sprintf(
"could not find matching Collection for locked item %v", string(i), "could not find matching Collection for locked item %v", string(i),
))) )))
continue continue
} }


if item, err = NewItem(c, i); err != nil { if item, err = NewItem(c, i); err != nil {
errs = append(errs, errors.New(fmt.Sprintf( errs.AddError(errors.New(fmt.Sprintf(
"could not create Item for locked item %v", string(i), "could not create Item for locked item %v; error follows", string(i),
))) )))
errs.AddError(err)
err = nil err = nil
continue continue
} }
@ -454,24 +465,25 @@ func (s *Service) SearchItems(attributes map[string]string) (unlockedItems []*It
cPath = dbus.ObjectPath(filepath.Dir(string(i))) cPath = dbus.ObjectPath(filepath.Dir(string(i)))


if c, ok = collections[cPath]; !ok { if c, ok = collections[cPath]; !ok {
errs = append(errs, errors.New(fmt.Sprintf( errs.AddError(errors.New(fmt.Sprintf(
"could not find matching Collection for unlocked item %v", string(i), "could not find matching Collection for unlocked item %v", string(i),
))) )))
continue continue
} }


if item, err = NewItem(c, i); err != nil { if item, err = NewItem(c, i); err != nil {
errs = append(errs, errors.New(fmt.Sprintf( errs.AddError(errors.New(fmt.Sprintf(
"could not create Item for unlocked item %v", string(i), "could not create Item for unlocked item %v; error follows", string(i),
))) )))
errs.AddError(err)
err = nil err = nil
continue continue
} }
unlockedItems = append(unlockedItems, item) unlockedItems = append(unlockedItems, item)
} }


if errs != nil && len(errs) > 0 { if !errs.IsEmpty() {
err = NewErrors(errs...) err = errs
} }


return return

View File

@ -6,16 +6,6 @@ import (
"github.com/godbus/dbus/v5" "github.com/godbus/dbus/v5"
) )


/*
MultiError is a type of error.Error that can contain multiple error.Errors. Confused? Don't worry about it.
*/
type MultiError struct {
// Errors is a slice of errors to combine/concatenate when .Error() is called.
Errors []error `json:"errors"`
// ErrorSep is a string to use to separate errors for .Error(). The default is "\n".
ErrorSep string `json:"separator"`
}

/* /*
SecretServiceError is a translated error from SecretService API. SecretServiceError is a translated error from SecretService API.
See https://developer-old.gnome.org/libsecret/unstable/libsecret-SecretError.html#SecretError and See https://developer-old.gnome.org/libsecret/unstable/libsecret-SecretError.html#SecretError and
@ -75,14 +65,16 @@ type Service struct {
// IsLocked indicates if the Service is locked or not. Status updated by Service.Locked. // IsLocked indicates if the Service is locked or not. Status updated by Service.Locked.
IsLocked bool `json:"locked"` IsLocked bool `json:"locked"`
/* /*
Legacy indicates that this SecretService implementation Legacy indicates that this SecretService implementation breaks current spec
breaks current spec by implementing the legacy/obsolete draft spec rather than current libsecret spec by implementing the legacy/obsolete draft spec rather than current libsecret spec
for the Dbus API. for the Dbus API.


If you're using SecretService with KeePassXC, for instance, or a much older version of Gnome-Keyring *before* libsecret integration(?), If you're using SecretService with KeePassXC, for instance, or a much older version
or if you are getting strange errors when performing a Service.SearchItems, you probably need to enable this field on the Service returned of Gnome-Keyring *before* libsecret integration(?), or if you are getting strange errors
by NewService. The coverage of this field may expand in the future, but currently it only prevents the (non-existent, in legacy spec) when performing a Service.SearchItems, you probably need to enable this field on the
Type property from being read or written on Items during NewItem and Collection.CreateItem. Service returned by NewService. The coverage of this field may expand in the future, but
currently it only prevents/suppresses the (non-existent, in legacy spec) Type property
from being read or written on Items during NewItem and Collection.CreateItem respectively.
*/ */
Legacy bool `json:"is_legacy"` Legacy bool `json:"is_legacy"`
} }