github.com/blend/go-sdk@v1.20220411.3/reflectutil/patch.go (about) 1 /* 2 3 Copyright (c) 2022 - Present. Blend Labs, Inc. All rights reserved 4 Use of this source code is governed by a MIT license that can be found in the LICENSE file. 5 6 */ 7 8 package reflectutil 9 10 import ( 11 "reflect" 12 13 "github.com/blend/go-sdk/ex" 14 ) 15 16 // Patcher describes an object that can be patched with raw values. 17 type Patcher interface { 18 Patch(map[string]interface{}) error 19 } 20 21 // Patch updates an object based on a map of field names to values. 22 func Patch(obj interface{}, patchValues map[string]interface{}) (err error) { 23 defer func() { 24 if r := recover(); r != nil { 25 err = ex.New(r) 26 } 27 }() 28 29 if patchable, isPatchable := obj.(Patcher); isPatchable { 30 return patchable.Patch(patchValues) 31 } 32 33 targetValue := Value(obj) 34 targetType := targetValue.Type() 35 36 for key, value := range patchValues { 37 err = SetValue(obj, targetType, targetValue, key, value) 38 if err != nil { 39 return err 40 } 41 } 42 return nil 43 } 44 45 // SetValue sets a value on an object by its field name. 46 func SetValue(obj interface{}, objType reflect.Type, objValue reflect.Value, fieldName string, value interface{}) (err error) { 47 defer func() { 48 if r := recover(); r != nil { 49 err = ex.New("panic setting value by name", ex.OptMessagef("field: %s panic: %v", fieldName, r)) 50 } 51 }() 52 53 relevantField, hasField := objType.FieldByName(fieldName) 54 if !hasField { 55 err = ex.New("unknown field", ex.OptMessagef("%s `%s`", objType.Name(), fieldName)) 56 return 57 } 58 59 return doSetValue(relevantField, objType, objValue, fieldName, value) 60 } 61 62 func doSetValue(relevantField reflect.StructField, objType reflect.Type, objValue reflect.Value, name string, value interface{}) (err error) { 63 field := objValue.FieldByName(relevantField.Name) 64 if !field.CanSet() { 65 err = ex.New("cannot set field", ex.OptMessagef("%s `%s`", objType.Name(), name)) 66 return 67 } 68 69 valueReflected := Value(value) 70 if !valueReflected.IsValid() { 71 err = ex.New("invalid value", ex.OptMessagef("%s `%s`", objType.Name(), name)) 72 return 73 } 74 75 assigned, assignErr := tryAssignment(field, valueReflected) 76 if assignErr != nil { 77 err = assignErr 78 return 79 } 80 if !assigned { 81 err = ex.New("cannot set field", ex.OptMessagef("%s `%s`", objType.Name(), name)) 82 return 83 } 84 return 85 }