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 }