github.com/vmware/govmomi@v0.51.0/fault/fault.go (about) 1 // © Broadcom. All Rights Reserved. 2 // The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. 3 // SPDX-License-Identifier: Apache-2.0 4 5 package fault 6 7 import ( 8 "reflect" 9 10 "github.com/vmware/govmomi/vim25/types" 11 ) 12 13 // As finds the first fault in the error's tree that matches target, and if one 14 // is found, sets the target to that fault value and returns the fault's 15 // localized message and true. Otherwise, false is returned. 16 // 17 // The tree is inspected according to the object type. If the object implements 18 // Golang's error interface, the Unwrap() error or Unwrap() []error methods are 19 // repeatedly checked for additional errors. If the object implements GoVmomi's 20 // BaseMethodFault or HasLocalizedMethodFault interfaces, the object is checked 21 // for an underlying FaultCause. When err wraps multiple errors or faults, err 22 // is examined followed by a depth-first traversal of its children. 23 // 24 // An error matches target if the error's concrete value is assignable to the 25 // value pointed to by target, or if the error has a method 26 // AsFault(BaseMethodFault) (string, bool) such that AsFault(BaseMethodFault) 27 // returns true. In the latter case, the AsFault method is responsible for 28 // setting target. 29 // 30 // An error type might provide an AsFault method so it can be treated as if it 31 // were a different error type. 32 // 33 // This function panics if err does not implement error, types.BaseMethodFault, 34 // types.HasLocalizedMethodFault, Fault() types.BaseMethodFault, or if target is 35 // not a pointer. 36 func As(err, target any) (localizedMessage string, okay bool) { 37 if err == nil { 38 return 39 } 40 if target == nil { 41 panic("fault: target cannot be nil") 42 } 43 val := reflect.ValueOf(target) 44 typ := val.Type() 45 if typ.Kind() != reflect.Ptr || val.IsNil() { 46 panic("fault: target must be a non-nil pointer") 47 } 48 targetType := typ.Elem() 49 if targetType.Kind() != reflect.Interface && 50 !targetType.Implements(baseMethodFaultType) { 51 panic("fault: *target must be interface or implement BaseMethodFault") 52 } 53 if !as(err, target, val, targetType, &localizedMessage) { 54 return "", false 55 } 56 return localizedMessage, true 57 } 58 59 func as( 60 err, 61 target any, 62 targetVal reflect.Value, 63 targetType reflect.Type, 64 localizedMsg *string) bool { 65 66 for { 67 if reflect.TypeOf(err).AssignableTo(targetType) { 68 targetVal.Elem().Set(reflect.ValueOf(err)) 69 return true 70 } 71 if tErr, ok := err.(hasAsFault); ok { 72 if msg, ok := tErr.AsFault(target); ok { 73 *localizedMsg = msg 74 return true 75 } 76 return false 77 } 78 switch tErr := err.(type) { 79 case types.HasLocalizedMethodFault: 80 if fault := tErr.GetLocalizedMethodFault(); fault != nil { 81 *localizedMsg = fault.LocalizedMessage 82 if fault.Fault != nil { 83 return as( 84 fault.Fault, 85 target, 86 targetVal, 87 targetType, 88 localizedMsg) 89 } 90 } 91 return false 92 case types.BaseMethodFault: 93 if fault := tErr.GetMethodFault(); fault != nil { 94 if fault.FaultCause != nil { 95 *localizedMsg = fault.FaultCause.LocalizedMessage 96 return as( 97 fault.FaultCause, 98 target, 99 targetVal, 100 targetType, 101 localizedMsg) 102 } 103 } 104 return false 105 case hasFault: 106 if fault := tErr.Fault(); fault != nil { 107 return as(fault, target, targetVal, targetType, localizedMsg) 108 } 109 return false 110 case unwrappableError: 111 if err = tErr.Unwrap(); err == nil { 112 return false 113 } 114 case unwrappableErrorSlice: 115 for _, err := range tErr.Unwrap() { 116 if err == nil { 117 continue 118 } 119 return as(err, target, targetVal, targetType, localizedMsg) 120 } 121 return false 122 default: 123 return false 124 } 125 } 126 } 127 128 // Is reports whether any fault in err's tree matches target. 129 // 130 // The tree is inspected according to the object type. If the object implements 131 // Golang's error interface, the Unwrap() error or Unwrap() []error methods are 132 // repeatedly checked for additional errors. If the object implements GoVmomi's 133 // BaseMethodFault or HasLocalizedMethodFault interfaces, the object is checked 134 // for an underlying FaultCause. When err wraps multiple errors or faults, err 135 // is examined followed by a depth-first traversal of its children. 136 // 137 // An error is considered to match a target if it is equal to that target or if 138 // it implements a method IsFault(BaseMethodFault) bool such that 139 // IsFault(BaseMethodFault) returns true. 140 // 141 // An error type might provide an IsFault method so it can be treated as 142 // equivalent to an existing fault. For example, if MyFault defines: 143 // 144 // func (m MyFault) IsFault(target BaseMethodFault) bool { 145 // return target == &types.NotSupported{} 146 // } 147 // 148 // then IsFault(MyError{}, &types.NotSupported{}) returns true. An IsFault 149 // method should only shallowly compare err and the target and not unwrap 150 // either. 151 func Is(err any, target types.BaseMethodFault) bool { 152 if target == nil { 153 return err == target 154 } 155 isComparable := reflect.TypeOf(target).Comparable() 156 return is(err, target, isComparable) 157 } 158 159 func is(err any, target types.BaseMethodFault, targetComparable bool) bool { 160 for { 161 if targetComparable && err == target { 162 return true 163 } 164 if tErr, ok := err.(hasIsFault); ok && tErr.IsFault(target) { 165 return true 166 } 167 switch tErr := err.(type) { 168 case types.HasLocalizedMethodFault: 169 fault := tErr.GetLocalizedMethodFault() 170 if fault == nil { 171 return false 172 } 173 err = fault.Fault 174 case types.BaseMethodFault: 175 if reflect.ValueOf(err).Type() == reflect.ValueOf(target).Type() { 176 return true 177 } 178 fault := tErr.GetMethodFault() 179 if fault == nil { 180 return false 181 } 182 err = fault.FaultCause 183 case hasFault: 184 if err = tErr.Fault(); err == nil { 185 return false 186 } 187 case unwrappableError: 188 if err = tErr.Unwrap(); err == nil { 189 return false 190 } 191 case unwrappableErrorSlice: 192 for _, err := range tErr.Unwrap() { 193 if is(err, target, targetComparable) { 194 return true 195 } 196 } 197 return false 198 default: 199 return false 200 } 201 } 202 } 203 204 // OnFaultFn is called for every fault encountered when inspecting an error 205 // or fault for a fault tree. The In function returns when the entire tree is 206 // inspected or the OnFaultFn returns true. 207 type OnFaultFn func( 208 fault types.BaseMethodFault, 209 localizedMessage string, 210 localizableMessages []types.LocalizableMessage) bool 211 212 // In invokes onFaultFn for each fault in err's tree. 213 // 214 // The tree is inspected according to the object type. If the object implements 215 // Golang's error interface, the Unwrap() error or Unwrap() []error methods are 216 // repeatedly checked for additional errors. If the object implements GoVmomi's 217 // BaseMethodFault or HasLocalizedMethodFault interfaces, the object is checked 218 // for an underlying FaultCause. When err wraps multiple errors or faults, err 219 // is examined followed by a depth-first traversal of its children. 220 // 221 // This function panics if err does not implement error, types.BaseMethodFault, 222 // types.HasLocalizedMethodFault, Fault() types.BaseMethodFault, or if onFaultFn 223 // is nil. 224 func In(err any, onFaultFn OnFaultFn) { 225 if onFaultFn == nil { 226 panic("fault: onFaultFn must not be nil") 227 } 228 switch tErr := err.(type) { 229 case types.HasLocalizedMethodFault: 230 inFault(tErr.GetLocalizedMethodFault(), onFaultFn) 231 case types.BaseMethodFault: 232 inFault(&types.LocalizedMethodFault{Fault: tErr}, onFaultFn) 233 case hasFault: 234 if fault := tErr.Fault(); fault != nil { 235 inFault(&types.LocalizedMethodFault{Fault: fault}, onFaultFn) 236 } 237 case unwrappableError: 238 In(tErr.Unwrap(), onFaultFn) 239 case unwrappableErrorSlice: 240 for _, uErr := range tErr.Unwrap() { 241 if uErr == nil { 242 continue 243 } 244 In(uErr, onFaultFn) 245 } 246 case error: 247 // No-op 248 default: 249 panic("fault: err must implement error, types.BaseMethodFault, or " + 250 "types.HasLocalizedMethodFault") 251 } 252 } 253 254 func inFault( 255 localizedMethodFault *types.LocalizedMethodFault, 256 onFaultFn OnFaultFn) { 257 258 if localizedMethodFault == nil { 259 return 260 } 261 262 fault := localizedMethodFault.Fault 263 if fault == nil { 264 return 265 } 266 267 var ( 268 faultCause *types.LocalizedMethodFault 269 faultMessages []types.LocalizableMessage 270 ) 271 272 if methodFault := fault.GetMethodFault(); methodFault != nil { 273 faultCause = methodFault.FaultCause 274 faultMessages = methodFault.FaultMessage 275 } 276 277 if onFaultFn(fault, localizedMethodFault.LocalizedMessage, faultMessages) { 278 return 279 } 280 281 // Check the fault's children. 282 inFault(faultCause, onFaultFn) 283 } 284 285 type hasFault interface { 286 Fault() types.BaseMethodFault 287 } 288 289 type hasAsFault interface { 290 AsFault(target any) (string, bool) 291 } 292 293 type hasIsFault interface { 294 IsFault(target types.BaseMethodFault) bool 295 } 296 297 type unwrappableError interface { 298 Unwrap() error 299 } 300 301 type unwrappableErrorSlice interface { 302 Unwrap() []error 303 } 304 305 var baseMethodFaultType = reflect.TypeOf((*types.BaseMethodFault)(nil)).Elem()