go.charczuk.com@v0.0.0-20240327042549-bc490516bd1a/sdk/errutil/multi.go (about)

     1  /*
     2  
     3  Copyright (c) 2023 - Present. Will Charczuk. All rights reserved.
     4  Use of this source code is governed by a MIT license that can be found in the LICENSE file at the root of the repository.
     5  
     6  */
     7  
     8  package errutil
     9  
    10  import (
    11  	"errors"
    12  	"fmt"
    13  	"strings"
    14  )
    15  
    16  // Multi represents an array of errors.
    17  type Multi []error
    18  
    19  // Error implements error.
    20  func (m Multi) Error() string {
    21  	if len(m) == 0 {
    22  		return ""
    23  	}
    24  	if len(m) == 1 {
    25  		return m[0].Error()
    26  	}
    27  	points := make([]string, len(m))
    28  	for i, err := range m {
    29  		points[i] = fmt.Sprintf("* %v", err)
    30  	}
    31  	return fmt.Sprintf(
    32  		"%d errors occurred:\n\t%s\n\n",
    33  		len(m), strings.Join(points, "\n\t"))
    34  }
    35  
    36  // WrappedErrors implements something in errors.
    37  func (m Multi) WrappedErrors() []error {
    38  	return m
    39  }
    40  
    41  // Unwrap returns an error from Error (or nil if there are no errors).
    42  // This error returned will further support Unwrap to get the next error,
    43  // etc.
    44  //
    45  // The resulting error supports errors.As/Is/Unwrap so you can continue
    46  // to use the stdlib errors package to introspect further.
    47  //
    48  // This will perform a shallow copy of the errors slice. Any errors appended
    49  // to this error after calling Unwrap will not be available until a new
    50  // Unwrap is called on the multierror.Error.
    51  func (m Multi) Unwrap() error {
    52  	if len(m) == 0 {
    53  		return nil
    54  	}
    55  	if len(m) == 1 {
    56  		return m[0]
    57  	}
    58  	errs := make([]error, len(m))
    59  	copy(errs, m)
    60  	return chain(errs)
    61  }
    62  
    63  // chain implements the interfaces necessary for errors.Is/As/Unwrap to
    64  // work in a deterministic way with multierror. A chain tracks a list of
    65  // errors while accounting for the current represented error. This lets
    66  // Is/As be meaningful.
    67  //
    68  // Unwrap returns the next error. In the cleanest form, Unwrap would return
    69  // the wrapped error here but we can't do that if we want to properly
    70  // get access to all the errors. Instead, users are recommended to use
    71  // Is/As to get the correct error type out.
    72  //
    73  // Precondition: []error is non-empty (len > 0)
    74  type chain []error
    75  
    76  // Error implements the error interface
    77  func (e chain) Error() string {
    78  	return e[0].Error()
    79  }
    80  
    81  // Unwrap implements errors.Unwrap by returning the next error in the
    82  // chain or nil if there are no more errors.
    83  func (e chain) Unwrap() error {
    84  	if len(e) == 1 {
    85  		return nil
    86  	}
    87  
    88  	return e[1:]
    89  }
    90  
    91  // As implements errors.As by attempting to map to the current value.
    92  func (e chain) As(target interface{}) bool {
    93  	return errors.As(e[0], target)
    94  }
    95  
    96  // Is implements errors.Is by comparing the current value directly.
    97  func (e chain) Is(target error) bool {
    98  	return errors.Is(e[0], target)
    99  }