-
-
Notifications
You must be signed in to change notification settings - Fork 18
/
cast.go
98 lines (82 loc) · 2.36 KB
/
cast.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 changeset
import (
"reflect"
"strings"
"github.com/Fs02/grimoire/params"
"github.com/azer/snakecase"
)
// CastErrorMessage is the default error message for Cast.
var CastErrorMessage = "{field} is invalid"
// Cast params as changes for the given data according to the permitted fields. Returns a new changeset.
// params will only be added as changes if it does not have the same value as the field in the data.
func Cast(data interface{}, params params.Params, fields []string, opts ...Option) *Changeset {
options := Options{
message: CastErrorMessage,
}
options.apply(opts)
var ch *Changeset
if existingCh, ok := data.(Changeset); ok {
ch = &existingCh
} else if existingCh, ok := data.(*Changeset); ok {
ch = existingCh
} else {
ch = &Changeset{}
ch.params = params
ch.changes = make(map[string]interface{})
ch.values, ch.types = mapSchema(data)
}
for _, field := range fields {
typ, texist := ch.types[field]
currentValue, vexist := ch.values[field]
if !params.Exists(field) || !texist {
continue
}
if value, valid := params.GetWithType(field, typ); valid {
if (typ.Kind() == reflect.Slice || typ.Kind() == reflect.Array) || (!vexist && value != nil) || (vexist && currentValue != value) {
ch.changes[field] = value
}
} else {
msg := strings.Replace(options.message, "{field}", field, 1)
AddError(ch, field, msg)
}
}
return ch
}
func mapSchema(data interface{}) (map[string]interface{}, map[string]reflect.Type) {
mvalues := make(map[string]interface{})
mtypes := make(map[string]reflect.Type)
rv := reflect.ValueOf(data)
if rv.Kind() == reflect.Ptr {
rv = rv.Elem()
}
rt := rv.Type()
if rv.Kind() != reflect.Struct {
panic("data must be a struct")
}
for i := 0; i < rv.NumField(); i++ {
fv := rv.Field(i)
ft := rt.Field(i)
var name string
if tag := ft.Tag.Get("db"); tag != "" {
if tag == "-" {
continue
}
name = tag
} else {
name = snakecase.SnakeCase(ft.Name)
}
if ft.Type.Kind() == reflect.Ptr {
mtypes[name] = ft.Type.Elem()
if !fv.IsNil() {
mvalues[name] = fv.Elem().Interface()
}
} else if ft.Type.Kind() == reflect.Slice && ft.Type.Elem().Kind() == reflect.Ptr {
mtypes[name] = reflect.SliceOf(ft.Type.Elem().Elem())
mvalues[name] = fv.Interface()
} else {
mtypes[name] = fv.Type()
mvalues[name] = fv.Interface()
}
}
return mvalues, mtypes
}