github.com/minio/mc@v0.0.0-20240507152021-646712d5e5fb/cmd/error.go (about)

     1  // Copyright (c) 2015-2022 MinIO, Inc.
     2  //
     3  // This file is part of MinIO Object Storage stack
     4  //
     5  // This program is free software: you can redistribute it and/or modify
     6  // it under the terms of the GNU Affero General Public License as published by
     7  // the Free Software Foundation, either version 3 of the License, or
     8  // (at your option) any later version.
     9  //
    10  // This program is distributed in the hope that it will be useful
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13  // GNU Affero General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Affero General Public License
    16  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17  
    18  package cmd
    19  
    20  import (
    21  	"context"
    22  	"errors"
    23  	"fmt"
    24  	"strings"
    25  	"unicode"
    26  
    27  	"github.com/minio/cli"
    28  	json "github.com/minio/colorjson"
    29  	"github.com/minio/mc/pkg/probe"
    30  	"github.com/minio/pkg/v2/console"
    31  )
    32  
    33  // causeMessage container for golang error messages
    34  type causeMessage struct {
    35  	Message string `json:"message"`
    36  	Error   error  `json:"error"`
    37  }
    38  
    39  // errorMessage container for error messages
    40  type errorMessage struct {
    41  	Message   string             `json:"message"`
    42  	Cause     causeMessage       `json:"cause"`
    43  	Type      string             `json:"type"`
    44  	CallTrace []probe.TracePoint `json:"trace,omitempty"`
    45  	SysInfo   map[string]string  `json:"sysinfo,omitempty"`
    46  }
    47  
    48  // fatalIf wrapper function which takes error and selectively prints stack frames if available on debug
    49  func fatalIf(err *probe.Error, msg string, data ...interface{}) {
    50  	if err == nil {
    51  		return
    52  	}
    53  	fatal(err, msg, data...)
    54  }
    55  
    56  func fatal(err *probe.Error, msg string, data ...interface{}) {
    57  	if globalJSON {
    58  		errorMsg := errorMessage{
    59  			Message: msg,
    60  			Type:    "fatal",
    61  			Cause: causeMessage{
    62  				Message: err.ToGoError().Error(),
    63  				Error:   err.ToGoError(),
    64  			},
    65  		}
    66  		if globalDebug {
    67  			errorMsg.CallTrace = err.CallTrace
    68  			errorMsg.SysInfo = err.SysInfo
    69  		}
    70  		json, e := json.MarshalIndent(struct {
    71  			Status string       `json:"status"`
    72  			Error  errorMessage `json:"error"`
    73  		}{
    74  			Status: "error",
    75  			Error:  errorMsg,
    76  		}, "", " ")
    77  		if e != nil {
    78  			console.Fatalln(probe.NewError(e))
    79  		}
    80  		console.Println(string(json))
    81  		console.Fatalln()
    82  	}
    83  
    84  	msg = fmt.Sprintf(msg, data...)
    85  	errmsg := err.String()
    86  	if !globalDebug {
    87  		var e error
    88  		if errors.Is(globalContext.Err(), context.Canceled) {
    89  			// mc is getting killed
    90  			e = errors.New("Canceling upon user request")
    91  		} else {
    92  			e = err.ToGoError()
    93  		}
    94  		errmsg = e.Error()
    95  	}
    96  
    97  	// Remove unnecessary leading spaces in generic/detailed error messages
    98  	msg = strings.TrimSpace(msg)
    99  	errmsg = strings.TrimSpace(errmsg)
   100  
   101  	// Add punctuations when needed
   102  	if len(errmsg) > 0 && len(msg) > 0 {
   103  		if msg[len(msg)-1] != ':' && msg[len(msg)-1] != '.' {
   104  			// The detailed error message starts with a capital letter,
   105  			// we should then add '.', otherwise add ':'.
   106  			if unicode.IsUpper(rune(errmsg[0])) {
   107  				msg += "."
   108  			} else {
   109  				msg += ":"
   110  			}
   111  		}
   112  		// Add '.' to the detail error if not found
   113  		if errmsg[len(errmsg)-1] != '.' {
   114  			errmsg += "."
   115  		}
   116  	}
   117  
   118  	console.Fatalln(fmt.Sprintf("%s %s", msg, errmsg))
   119  }
   120  
   121  // Exit coder wraps cli new exit error with a
   122  // custom exitStatus number. cli package requires
   123  // an error with `cli.ExitCoder` compatibility
   124  // after an action. Which woud allow cli package to
   125  // exit with the specified `exitStatus`.
   126  func exitStatus(status int) error {
   127  	return cli.NewExitError("", status)
   128  }
   129  
   130  // errorIf synonymous with fatalIf but doesn't exit on error != nil
   131  func errorIf(err *probe.Error, msg string, data ...interface{}) {
   132  	if err == nil {
   133  		return
   134  	}
   135  	if globalJSON {
   136  		errorMsg := errorMessage{
   137  			Message: fmt.Sprintf(msg, data...),
   138  			Type:    "error",
   139  			Cause: causeMessage{
   140  				Message: err.ToGoError().Error(),
   141  				Error:   err.ToGoError(),
   142  			},
   143  		}
   144  		if globalDebug {
   145  			errorMsg.CallTrace = err.CallTrace
   146  			errorMsg.SysInfo = err.SysInfo
   147  		}
   148  		json, e := json.MarshalIndent(struct {
   149  			Status string       `json:"status"`
   150  			Error  errorMessage `json:"error"`
   151  		}{
   152  			Status: "error",
   153  			Error:  errorMsg,
   154  		}, "", " ")
   155  		if e != nil {
   156  			console.Fatalln(probe.NewError(e))
   157  		}
   158  		console.Println(string(json))
   159  		return
   160  	}
   161  	msg = fmt.Sprintf(msg, data...)
   162  	if !globalDebug {
   163  		var e error
   164  		if errors.Is(globalContext.Err(), context.Canceled) {
   165  			// mc is getting killed
   166  			e = errors.New("Canceling upon user request")
   167  		} else {
   168  			e = err.ToGoError()
   169  		}
   170  		console.Errorln(fmt.Sprintf("%s %s", msg, e))
   171  		return
   172  	}
   173  	console.Errorln(fmt.Sprintf("%s %s", msg, err))
   174  }
   175  
   176  // deprecatedError function for deprecated commands
   177  func deprecatedError(newCommandName string) {
   178  	err := probe.NewError(fmt.Errorf("Please use '%s' instead", newCommandName))
   179  	fatal(err, "Deprecated command")
   180  }
   181  
   182  // deprecatedError function for deprecated flags
   183  func deprecatedFlagError(oldFlag, newFlag string) {
   184  	err := probe.NewError(fmt.Errorf("'%s' has been deprecated, please use %s instead", oldFlag, newFlag))
   185  	fatal(err, "a deprecated Flag")
   186  }
   187  
   188  func deprecatedFlagsWarning(cliCtx *cli.Context) {
   189  	for _, v := range cliCtx.Args() {
   190  		switch v {
   191  		case "--encrypt", "-encrypt":
   192  			deprecatedFlagError("--encrypt", "--enc-s3 or --enc-kms")
   193  		case "--encrypt-key", "-encrypt-key":
   194  			deprecatedFlagError("--encrypt-key", "--enc-c")
   195  		}
   196  	}
   197  }