github.com/turbot/steampipe@v1.7.0-rc.0.0.20240517123944-7cef272d4458/pkg/error_helpers/utils.go (about)

     1  package error_helpers
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"os"
     8  	"strings"
     9  
    10  	"golang.org/x/exp/maps"
    11  
    12  	"github.com/fatih/color"
    13  	"github.com/shiena/ansicolor"
    14  	"github.com/spf13/viper"
    15  	"github.com/turbot/steampipe/pkg/constants"
    16  	"github.com/turbot/steampipe/pkg/statushooks"
    17  )
    18  
    19  func init() {
    20  	color.Output = ansicolor.NewAnsiColorWriter(os.Stderr)
    21  }
    22  
    23  func WrapError(err error) error {
    24  	if err == nil {
    25  		return nil
    26  	}
    27  	return HandleCancelError(err)
    28  }
    29  
    30  func FailOnError(err error) {
    31  	if err != nil {
    32  		err = HandleCancelError(err)
    33  		panic(err)
    34  	}
    35  }
    36  
    37  func FailOnErrorWithMessage(err error, message string) {
    38  	if err != nil {
    39  		err = HandleCancelError(err)
    40  		panic(fmt.Sprintf("%s: %s", message, err.Error()))
    41  	}
    42  }
    43  
    44  func ShowError(ctx context.Context, err error) {
    45  	if err == nil {
    46  		return
    47  	}
    48  	err = HandleCancelError(err)
    49  	statushooks.Done(ctx)
    50  	fmt.Fprintf(color.Error, "%s: %v\n", constants.ColoredErr, TransformErrorToSteampipe(err))
    51  }
    52  
    53  // ShowErrorWithMessage displays the given error nicely with the given message
    54  func ShowErrorWithMessage(ctx context.Context, err error, message string) {
    55  	if err == nil {
    56  		return
    57  	}
    58  	err = HandleCancelError(err)
    59  	statushooks.Done(ctx)
    60  	fmt.Fprintf(color.Error, "%s: %s - %v\n", constants.ColoredErr, message, TransformErrorToSteampipe(err))
    61  }
    62  
    63  // TransformErrorToSteampipe removes the pq: and rpc error prefixes along
    64  // with all the unnecessary information that comes from the
    65  // drivers and libraries
    66  func TransformErrorToSteampipe(err error) error {
    67  	if err == nil {
    68  		return err
    69  	}
    70  	// transform to a context
    71  	err = HandleCancelError(err)
    72  
    73  	errString := strings.TrimSpace(err.Error())
    74  
    75  	// an error that originated from our database/sql driver (always prefixed with "ERROR:")
    76  	if strings.HasPrefix(errString, "ERROR:") {
    77  		errString = strings.TrimSpace(strings.TrimPrefix(errString, "ERROR:"))
    78  
    79  		// if this is an RPC Error while talking with the plugin
    80  		if strings.HasPrefix(errString, "rpc error") {
    81  			// trim out "rpc error: code = Unknown desc ="
    82  			errString = strings.TrimPrefix(errString, "rpc error: code = Unknown desc =")
    83  		}
    84  	}
    85  	return fmt.Errorf(strings.TrimSpace(errString))
    86  }
    87  
    88  // HandleCancelError modifies a context.Canceled error into a readable error that can
    89  // be printed on the console
    90  func HandleCancelError(err error) error {
    91  	if IsCancelledError(err) {
    92  		err = errors.New("execution cancelled")
    93  	}
    94  
    95  	return err
    96  }
    97  
    98  func HandleQueryTimeoutError(err error) error {
    99  	if errors.Is(err, context.DeadlineExceeded) {
   100  		err = fmt.Errorf("query timeout exceeded (%ds)", viper.GetInt(constants.ArgDatabaseQueryTimeout))
   101  	}
   102  	return err
   103  }
   104  
   105  func IsCancelledError(err error) bool {
   106  	return errors.Is(err, context.Canceled) || strings.Contains(err.Error(), "canceling statement due to user request")
   107  }
   108  
   109  func ShowWarning(warning string) {
   110  	if len(warning) == 0 {
   111  		return
   112  	}
   113  	fmt.Fprintf(color.Error, "%s: %v\n", constants.ColoredWarn, warning)
   114  }
   115  
   116  func CombineErrorsWithPrefix(prefix string, errors ...error) error {
   117  	if len(errors) == 0 {
   118  		return nil
   119  	}
   120  
   121  	if allErrorsNil(errors...) {
   122  		return nil
   123  	}
   124  
   125  	if len(errors) == 1 {
   126  		if len(prefix) == 0 {
   127  			return errors[0]
   128  		} else {
   129  			return fmt.Errorf("%s - %s", prefix, errors[0].Error())
   130  		}
   131  	}
   132  
   133  	combinedErrorString := map[string]struct{}{prefix: {}}
   134  	for _, e := range errors {
   135  		if e == nil {
   136  			continue
   137  		}
   138  		combinedErrorString[e.Error()] = struct{}{}
   139  	}
   140  
   141  	return fmt.Errorf(strings.Join(maps.Keys(combinedErrorString), "\n\t"))
   142  }
   143  
   144  func allErrorsNil(errors ...error) bool {
   145  	for _, e := range errors {
   146  		if e != nil {
   147  			return false
   148  		}
   149  	}
   150  	return true
   151  }
   152  
   153  func CombineErrors(errors ...error) error {
   154  	return CombineErrorsWithPrefix("", errors...)
   155  }
   156  
   157  func PrefixError(err error, prefix string) error {
   158  	return fmt.Errorf("%s: %s\n", prefix, TransformErrorToSteampipe(err).Error())
   159  }