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  }