forked from Jacobbrewer1/patcher
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathloader.go
98 lines (81 loc) · 3.15 KB
/
loader.go
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
package patcher
import (
"errors"
"reflect"
)
var (
// ErrInvalidType is returned when the provided type is not a pointer to a struct
ErrInvalidType = errors.New("invalid type: must pointer to struct")
)
// LoadDiff inserts the fields from the new struct pointer into the old struct pointer, updating the old struct.
//
// Note that it only updates non-zero value fields, meaning you cannot set any field to zero, the empty string, etc.
// This behavior is configurable by setting the includeZeroValues option to true or for nil values by setting includeNilValues.
// Please see the LoaderOption's for more configuration options.
//
// This function is useful if you are inserting a patch into an existing object but require a new object to be returned with
// all fields updated.
func LoadDiff[T any](old, newT *T, opts ...PatchOpt) error {
return newPatchDefaults(opts...).loadDiff(old, newT)
}
// loadDiff inserts the fields provided in the new struct pointer into the old struct pointer and injects the new
// values into the old struct. It only pushes non-zero value updates, meaning you cannot set any field to zero,
// the empty string, etc. This is configurable by setting the includeZeroValues option to true or for nil values
// by setting includeNilValues. It handles embedded structs and recursively calls loadDiff for nested structs.
func (s *SQLPatch) loadDiff(old, newT any) error {
if !isPointerToStruct(old) || !isPointerToStruct(newT) {
return ErrInvalidType
}
oElem := reflect.ValueOf(old).Elem()
nElem := reflect.ValueOf(newT).Elem()
for i := range oElem.NumField() {
oField := oElem.Field(i)
nField := nElem.Field(i)
// Include only exported fields
if !oField.CanSet() || !nField.CanSet() {
continue
}
oldField := oElem.Type().Field(i)
// Handle embedded structs (Anonymous fields)
if oldField.Anonymous {
if err := s.handleEmbeddedStruct(oField, nField, oldField.Tag.Get(s.tagName)); err != nil {
return err
}
continue
}
// If the field is a struct, we need to recursively call LoadDiff
if oField.Kind() == reflect.Struct {
if err := s.loadDiff(oField.Addr().Interface(), nField.Addr().Interface()); err != nil {
return err
}
continue
}
// See if the field should be ignored.
if s.checkSkipField(&oldField) {
continue
}
patcherOptsTag := oldField.Tag.Get(TagOptsName)
// Compare the old and new fields.
//
// New fields take priority over old fields if they are provided based on the configuration.
if nField.Kind() != reflect.Ptr && (!nField.IsZero() || s.shouldIncludeZero(patcherOptsTag)) {
oField.Set(nField)
} else if nField.Kind() == reflect.Ptr && (!nField.IsNil() || s.shouldIncludeNil(patcherOptsTag)) {
oField.Set(nField)
}
}
return nil
}
func (s *SQLPatch) handleEmbeddedStruct(oField, nField reflect.Value, tag string) error {
if oField.Kind() != reflect.Ptr {
return s.loadDiff(oField.Addr().Interface(), nField.Addr().Interface())
}
switch {
case !oField.IsNil() && !nField.IsNil():
return s.loadDiff(oField.Interface(), nField.Interface())
case nField.IsValid() && !nField.IsNil(),
nField.IsNil() && s.shouldIncludeNil(tag):
oField.Set(nField)
}
return nil
}