blackmagic.go 1.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455
  1. package blackmagic
  2. import (
  3. "reflect"
  4. "github.com/pkg/errors"
  5. )
  6. // AssignIfCompatible is a convenience function to safely
  7. // assign arbitrary values. dst must be a pointer to an
  8. // empty interface, or it must be a pointer to a compatible
  9. // variable type that can hold src.
  10. func AssignIfCompatible(dst, src interface{}) error {
  11. orv := reflect.ValueOf(src) // save this value for error reporting
  12. result := orv
  13. // t can be a pointer or a slice, and the code will slightly change
  14. // depending on this
  15. var isSlice bool
  16. switch result.Kind() {
  17. case reflect.Ptr:
  18. // no op
  19. case reflect.Slice:
  20. isSlice = true
  21. default:
  22. return errors.Errorf("argument t to AssignIfCompatible must be a pointer or a slice: %T", src)
  23. }
  24. rv := reflect.ValueOf(dst)
  25. if rv.Kind() != reflect.Ptr {
  26. return errors.Errorf(`argument to AssignIfCompatible() must be a pointer: %T`, dst)
  27. }
  28. actualDst := rv.Elem()
  29. switch actualDst.Kind() {
  30. case reflect.Interface:
  31. // If it's an interface, we can just assign the pointer to the interface{}
  32. default:
  33. // If it's a pointer to the struct we're looking for, we need to set
  34. // the de-referenced struct
  35. if !isSlice {
  36. result = result.Elem()
  37. }
  38. }
  39. if !result.Type().AssignableTo(actualDst.Type()) {
  40. return errors.Errorf(`argument to AssignIfCompatible() must be compatible with %T (was %T)`, orv.Interface(), dst)
  41. }
  42. if !actualDst.CanSet() {
  43. return errors.Errorf(`argument to AssignIfCompatible() must be settable`)
  44. }
  45. actualDst.Set(result)
  46. return nil
  47. }