File: assign.go

package info (click to toggle)
golang-github-thoas-go-funk 0.9.3-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, forky, sid, trixie
  • size: 504 kB
  • sloc: makefile: 10
file content (129 lines) | stat: -rw-r--r-- 4,456 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
package funk

import (
	"errors"
	"fmt"
	"reflect"
	"strings"
)

// Set assigns in at path with value val. i.e. in.path = val
// in accepts types of ptr to struct, ptr to variable, slice and ptr to slice.
// Along the path, interface{} is supported and nil ptr is initialized to ptr to zero value
// of the type until the variable to be set is obtained.
// It returns errors when encountering along the path unknown types, uninitialized
// interface{} or interface{} containing struct directly (not ptr to struct).
//
// Slice is resolved the same way in funk.Get(), by traversing each element of the slice,
// so that each element of the slice's corresponding field are going to be set to the same provided val.
// If Set is called on slice with empty path "", it behaves the same as funk.Fill()
//
// If in is well formed, i.e. do not expect above descripted errors to happen, funk.MustSet()
// is a short hand wrapper to discard error return
func Set(in interface{}, val interface{}, path string) error {
	if in == nil {
		return errors.New("Cannot Set nil")
	}
	parts := []string{}
	if path != "" {
		parts = strings.Split(path, ".")
	}
	return setByParts(in, val, parts)
}

// we need this layer to handle interface{} type
func setByParts(in interface{}, val interface{}, parts []string) error {

	if in == nil {
		// nil interface can happen during traversing the path
		return errors.New("Cannot traverse nil/uninitialized interface{}")
	}

	inValue := reflect.ValueOf(in)
	inKind := inValue.Type().Kind()

	// Note: if interface contains a struct (not ptr to struct) then the content of the struct cannot be set.
	// I.e. it is not CanAddr() or CanSet()
	// So we require in interface{} to be a ptr, slice or array
	if inKind == reflect.Ptr {
		inValue = inValue.Elem() // if it is ptr we set its content not ptr its self
	} else if inKind != reflect.Array && inKind != reflect.Slice {
		return fmt.Errorf("Type %s not supported by Set", inValue.Type().String())
	}

	return set(inValue, reflect.ValueOf(val), parts)
}

// traverse inValue using path in parts and set the dst to be setValue
func set(inValue reflect.Value, setValue reflect.Value, parts []string) error {

	// traverse the path to get the inValue we need to set
	i := 0
	for i < len(parts) {

		kind := inValue.Kind()

		switch kind {
		case reflect.Invalid:
			// do not expect this case to happen
			return errors.New("nil pointer found along the path")
		case reflect.Struct:
			fValue := inValue.FieldByName(parts[i])
			if !fValue.IsValid() {
				return fmt.Errorf("field name %v is not found in struct %v", parts[i], inValue.Type().String())
			}
			if !fValue.CanSet() {
				return fmt.Errorf("field name %v is not exported in struct %v", parts[i], inValue.Type().String())
			}
			inValue = fValue
			i++
		case reflect.Slice, reflect.Array:
			// set all its elements
			length := inValue.Len()
			for j := 0; j < length; j++ {
				err := set(inValue.Index(j), setValue, parts[i:])
				if err != nil {
					return err
				}
			}
			return nil
		case reflect.Ptr:
			// only traverse down one level
			if inValue.IsNil() {
				// we initialize nil ptr to ptr to zero value of the type
				// and continue traversing
				inValue.Set(reflect.New(inValue.Type().Elem()))
			}
			// traverse the ptr until it is not pointer any more or is nil again
			inValue = redirectValue(inValue)
		case reflect.Interface:
			// Note: if interface contains a struct (not ptr to struct) then the content of the struct cannot be set.
			// I.e. it is not CanAddr() or CanSet(). This is why setByParts has a nil ptr check.
			// we treat this as a new call to setByParts, and it will do proper check of the types
			return setByParts(inValue.Interface(), setValue.Interface(), parts[i:])
		default:
			return fmt.Errorf("kind %v in path %v is not supported", kind, parts[i])
		}

	}
	// here inValue holds the value we need to set

	// interface{} can be set to any val
	// other types we ensure the type matches
	if inValue.Kind() != setValue.Kind() && inValue.Kind() != reflect.Interface {
		return fmt.Errorf("cannot set target of type %v with type %v", inValue.Kind(), setValue.Kind())
	}
	inValue.Set(setValue)

	return nil
}

// MustSet is functionally the same as Set.
// It panics instead of returning error.
// It is safe to use if the in value is well formed.
func MustSet(in interface{}, val interface{}, path string) {
	err := Set(in, val, path)
	if err != nil {
		panic(err)
	}
}