mirror of
https://github.com/anchore/syft.git
synced 2025-11-17 08:23:15 +01:00
Signed-off-by: Keith Zantow <kzantow@gmail.com> Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com>
202 lines
6.0 KiB
Go
202 lines
6.0 KiB
Go
package unknown
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/anchore/syft/internal/log"
|
|
"github.com/anchore/syft/syft/file"
|
|
)
|
|
|
|
type hasCoordinates interface {
|
|
GetCoordinates() file.Coordinates
|
|
}
|
|
|
|
type CoordinateError struct {
|
|
Coordinates file.Coordinates
|
|
Reason error
|
|
}
|
|
|
|
var _ error = (*CoordinateError)(nil)
|
|
|
|
func (u *CoordinateError) Error() string {
|
|
if u.Coordinates.FileSystemID == "" {
|
|
return fmt.Sprintf("%s: %v", u.Coordinates.RealPath, u.Reason)
|
|
}
|
|
return fmt.Sprintf("%s (%s): %v", u.Coordinates.RealPath, u.Coordinates.FileSystemID, u.Reason)
|
|
}
|
|
|
|
// New returns a new CoordinateError unless the reason is a CoordinateError itself, in which case
|
|
// reason will be returned directly or if reason is nil, nil will be returned
|
|
func New(coords hasCoordinates, reason error) *CoordinateError {
|
|
if reason == nil {
|
|
return nil
|
|
}
|
|
coordinates := coords.GetCoordinates()
|
|
reasonCoordinateError := &CoordinateError{}
|
|
if errors.As(reason, &reasonCoordinateError) {
|
|
// if the reason is already a coordinate error, it is potentially for a different location,
|
|
// so we do not want to surface this location having an error
|
|
return reasonCoordinateError
|
|
}
|
|
return &CoordinateError{
|
|
Coordinates: coordinates,
|
|
Reason: reason,
|
|
}
|
|
}
|
|
|
|
// Newf returns a new CoordinateError with a reason of an error created from given format and args
|
|
func Newf(coords hasCoordinates, format string, args ...any) *CoordinateError {
|
|
return New(coords, fmt.Errorf(format, args...))
|
|
}
|
|
|
|
// Append returns an error joined to the first error/set of errors, with a new CoordinateError appended to the end
|
|
func Append(errs error, coords hasCoordinates, reason error) error {
|
|
return Join(errs, New(coords, reason))
|
|
}
|
|
|
|
// Appendf returns an error joined to the first error/set of errors, with a new CoordinateError appended to the end,
|
|
// created from the given reason and args
|
|
func Appendf(errs error, coords hasCoordinates, format string, args ...any) error {
|
|
return Append(errs, coords, fmt.Errorf(format, args...))
|
|
}
|
|
|
|
// Join joins the provided sets of errors together in a flattened manner, taking into account nested errors created
|
|
// from other sources, including errors.Join, multierror.Append, and unknown.Join
|
|
func Join(errs ...error) error {
|
|
var out []error
|
|
for _, err := range errs {
|
|
// append errors, de-duplicated
|
|
for _, e := range flatten(err) {
|
|
if containsErr(out, e) {
|
|
continue
|
|
}
|
|
out = append(out, e)
|
|
}
|
|
}
|
|
if len(out) == 1 {
|
|
return out[0]
|
|
}
|
|
if len(out) == 0 {
|
|
return nil
|
|
}
|
|
return errors.Join(out...)
|
|
}
|
|
|
|
// Joinf joins the provided sets of errors together in a flattened manner, taking into account nested errors created
|
|
// from other sources, including errors.Join, multierror.Append, and unknown.Join and appending a new error,
|
|
// created from the format and args provided -- the error is NOT a CoordinateError
|
|
func Joinf(errs error, format string, args ...any) error {
|
|
return Join(errs, fmt.Errorf(format, args...))
|
|
}
|
|
|
|
// IfEmptyf returns a new Errorf-formatted error, only when the provided slice is empty or nil when
|
|
// the slice has entries
|
|
func IfEmptyf[T any](emptyTest []T, format string, args ...any) error {
|
|
if len(emptyTest) == 0 {
|
|
return fmt.Errorf(format, args...)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ExtractCoordinateErrors extracts all coordinate errors returned, and any _additional_ errors in the graph
|
|
// are encapsulated in the second, error return parameter
|
|
func ExtractCoordinateErrors(err error) (coordinateErrors []CoordinateError, remainingErrors error) {
|
|
remainingErrors = visitErrors(err, func(e error) error {
|
|
if coordinateError, _ := e.(*CoordinateError); coordinateError != nil {
|
|
coordinateErrors = append(coordinateErrors, *coordinateError)
|
|
return nil
|
|
}
|
|
return e
|
|
})
|
|
return coordinateErrors, remainingErrors
|
|
}
|
|
|
|
func flatten(errs ...error) []error {
|
|
var out []error
|
|
for _, err := range errs {
|
|
if err == nil {
|
|
continue
|
|
}
|
|
// turn all errors nested under a coordinate error to individual coordinate errors
|
|
if e, ok := err.(*CoordinateError); ok {
|
|
if e == nil {
|
|
continue
|
|
}
|
|
for _, r := range flatten(e.Reason) {
|
|
out = append(out, New(e.Coordinates, r))
|
|
}
|
|
} else
|
|
// from multierror.Append
|
|
if e, ok := err.(interface{ WrappedErrors() []error }); ok {
|
|
if e == nil {
|
|
continue
|
|
}
|
|
out = append(out, flatten(e.WrappedErrors()...)...)
|
|
} else
|
|
// from errors.Join
|
|
if e, ok := err.(interface{ Unwrap() []error }); ok {
|
|
if e == nil {
|
|
continue
|
|
}
|
|
out = append(out, flatten(e.Unwrap()...)...)
|
|
} else {
|
|
out = append(out, err)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
// containsErr returns true if a duplicate error is found
|
|
func containsErr(out []error, err error) bool {
|
|
defer func() {
|
|
if err := recover(); err != nil {
|
|
log.Tracef("error comparing errors: %v", err)
|
|
}
|
|
}()
|
|
for _, e := range out {
|
|
if e == err {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// visitErrors visits every wrapped error. the returned error replaces the provided error, null errors are omitted from
|
|
// the object graph
|
|
func visitErrors(err error, fn func(error) error) error {
|
|
// unwrap errors from errors.Join
|
|
if errs, ok := err.(interface{ Unwrap() []error }); ok {
|
|
var out []error
|
|
for _, e := range errs.Unwrap() {
|
|
out = append(out, visitErrors(e, fn))
|
|
}
|
|
// errors.Join omits nil errors and will return nil if all passed errors are nil
|
|
return errors.Join(out...)
|
|
}
|
|
// unwrap errors from multierror.Append -- these also implement Unwrap() error, so check this first
|
|
if errs, ok := err.(interface{ WrappedErrors() []error }); ok {
|
|
var out []error
|
|
for _, e := range errs.WrappedErrors() {
|
|
out = append(out, visitErrors(e, fn))
|
|
}
|
|
// errors.Join omits nil errors and will return nil if all passed errors are nil
|
|
return errors.Join(out...)
|
|
}
|
|
// unwrap singly wrapped errors
|
|
if e, ok := err.(interface{ Unwrap() error }); ok {
|
|
wrapped := e.Unwrap()
|
|
got := visitErrors(wrapped, fn)
|
|
if got == nil {
|
|
return nil
|
|
}
|
|
if wrapped.Error() != got.Error() {
|
|
prefix := strings.TrimSuffix(err.Error(), wrapped.Error())
|
|
return fmt.Errorf("%s%w", prefix, got)
|
|
}
|
|
return err
|
|
}
|
|
return fn(err)
|
|
}
|