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()