// Copyright 2022 schukai GmbH // SPDX-License-Identifier: AGPL-3.0 package pathfinder import ( "fmt" "reflect" "strconv" "strings" ) // This function sets the value of a field in a struct, given a path to the field. func SetValue[D any](obj D, keyWithDots string, newValue any) error { keySlice := strings.Split(keyWithDots, ".") v := reflect.ValueOf(obj) for _, key := range keySlice[0 : len(keySlice)-1] { for v.Kind() != reflect.Ptr { if v.Kind() == reflect.Invalid { return newInvalidPathError(keyWithDots) } v = v.Addr() } if v.Kind() != reflect.Ptr { return newUnsupportedTypePathError(keyWithDots, v.Type()) } elem := v.Elem() if elem.Kind() != reflect.Struct { return newUnsupportedTypePathError(keyWithDots, v.Type()) } v = elem.FieldByName(key) } if v.Kind() == reflect.Invalid { return newInvalidPathError(keyWithDots) } for v.Kind() == reflect.Ptr { v = v.Elem() } // non-supporter type at the top of the path if v.Kind() != reflect.Struct { return newUnsupportedTypeAtTopOfPathError(keyWithDots, v.Type()) } v = v.FieldByName(keySlice[len(keySlice)-1]) if !v.IsValid() { return newInvalidPathError(keyWithDots) } if !v.CanSet() { return newCannotSetError(keyWithDots) } switch v.Kind() { case reflect.Ptr: if newValue == nil { v.Set(reflect.Zero(v.Type())) } else { v.Set(reflect.ValueOf(&newValue)) } return nil } newValueType := reflect.TypeOf(newValue) if newValueType == nil { return newUnsupportedTypePathError(keyWithDots, v.Type()) } newValueKind := reflect.TypeOf(newValue).Kind() switch v.Kind() { case reflect.String: if newValueKind == reflect.String { v.SetString(newValue.(string)) } else { v.SetString(fmt.Sprintf("%v", newValue)) } case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int: if newValueKind == reflect.Int { v.SetInt(int64(newValue.(int))) } else { s, err := strconv.ParseInt(newValue.(string), 10, 64) if err != nil { return err } v.SetInt(s) } case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: if newValueKind == reflect.Int { v.SetUint(uint64(newValue.(int))) } else { s, err := strconv.ParseInt(newValue.(string), 10, 64) if err != nil { return err } v.SetUint(uint64(s)) } case reflect.Bool: if newValueKind == reflect.Bool { v.SetBool(newValue.(bool)) } else { b, err := strconv.ParseBool(newValue.(string)) if err != nil { return err } v.SetBool(b) } case reflect.Float64, reflect.Float32: if newValueKind == reflect.Float64 { v.SetFloat(newValue.(float64)) } else { s, err := strconv.ParseFloat(newValue.(string), 64) if err != nil { return err } v.SetFloat(s) } case reflect.Slice, reflect.Array: if newValueKind == reflect.Ptr { newValue = reflect.ValueOf(newValue).Elem().Interface() v.Set(reflect.ValueOf(newValue)) } else if newValueKind == reflect.Slice { v.Set(reflect.ValueOf(newValue)) } else { return newUnsupportedTypePathError(keyWithDots, v.Type()) } default: return newInvalidTypeForPathError(keyWithDots, v.Type().String(), newValueKind.String()) } return nil }