-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathfactory.go
254 lines (215 loc) · 6.9 KB
/
factory.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
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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
package factory
import (
"fmt"
"reflect"
)
// Ctx is the context in which the field value is being generated
type Ctx struct {
Field string // current field name for which the value is generated
Instance interface{} // the result instance to that the field belongs
Factory *Factory // the reference to the Factory
}
// GeneratorFunc describes field generator signatures
type GeneratorFunc func(ctx Ctx) (interface{}, error)
// FieldGenFunc is the signature of field generator factory.
type FieldGenFunc func(sample reflect.Value) []fieldWithGen
// fieldWithGen is a tuple that keeps together struct field and generator function.
type fieldWithGen struct {
*reflect.StructField
gen GeneratorFunc
}
// Factory produces new objects according to specified generators
type Factory struct {
typ reflect.Type // type information about generated instances
fieldGens []fieldWithGen // field / generator tuples
callDepth int // factory call depth
}
// dive clones factory with incremented call depth
func (f *Factory) dive() *Factory {
return &Factory{
typ: f.typ,
fieldGens: f.fieldGens,
callDepth: f.callDepth + 1,
}
}
// CallDepth returns factory call depth
func (f *Factory) CallDepth() int {
return f.callDepth
}
// Derive produces a new factory overriding field generators
// with the list provided.
func (f *Factory) Derive(fieldGenFuncs ...FieldGenFunc) *Factory {
// Create new generators and lookup map to fast find generator by firld name
newGenList := make([]fieldWithGen, 0, len(fieldGenFuncs))
newGensMap := make(map[string]GeneratorFunc)
sample := f.new()
for _, fieldGenFunc := range fieldGenFuncs {
for _, fg := range fieldGenFunc(sample) {
newGensMap[fg.Name] = fg.gen
newGenList = append(newGenList, fg)
}
}
// result generators for a new factory
fieldGens := make([]fieldWithGen, len(f.fieldGens))
// 1. copy or override original field generators
for i, fg := range f.fieldGens {
if gen, ok := newGensMap[fg.Name]; ok {
delete(newGensMap, fg.Name)
fg.gen = gen
}
fieldGens[i] = fg
}
// 2. append new field generators
for _, fg := range newGenList {
if _, ok := newGensMap[fg.Name]; ok {
fieldGens = append(fieldGens, fg)
}
}
return &Factory{
callDepth: f.callDepth, // inherit currenet call depth
fieldGens: fieldGens, // set new generators
typ: f.typ,
}
}
func (f *Factory) new() reflect.Value {
return reflect.New(f.typ)
}
// SetFields fills in the struct instance fields
func (f *Factory) SetFields(i interface{}, fieldGenFuncs ...FieldGenFunc) error {
if len(fieldGenFuncs) > 0 {
return f.Derive(fieldGenFuncs...).SetFields(i)
}
// create execution context
ctx := Ctx{Instance: i, Factory: f.dive()}
elem := reflect.ValueOf(i).Elem()
for _, fg := range f.fieldGens {
// bind field name o context
ctx.Field = fg.Name
// generate field value
val, err := fg.gen(ctx)
if err != nil {
return err
}
valueof := reflect.ValueOf(val)
switch valueof.Kind() {
case reflect.Ptr:
// deref pointer if field is not a pointer kind
if fg.Type.Kind() != reflect.Ptr {
valueof = valueof.Elem()
}
case reflect.Invalid:
// for example we are here if fg.generator(ctx) returns (nil, nil)
valueof = reflect.Zero(fg.Type)
}
// find field by index
field := elem.FieldByIndex(fg.Index)
// and assign value to field
field.Set(valueof)
}
return nil
}
// MustSetFields calls SetFields and panics on error
func (f *Factory) MustSetFields(i interface{}, fieldGenFuncs ...FieldGenFunc) {
if err := f.SetFields(i, fieldGenFuncs...); err != nil {
panic(err)
}
}
// Create makes a new instance
func (f *Factory) Create(fieldGenFuncs ...FieldGenFunc) (interface{}, error) {
// allocate a new instance
instance := f.new()
if err := f.SetFields(instance.Interface(), fieldGenFuncs...); err != nil {
return nil, err
}
return instance.Interface(), nil
}
// MustCreate creates or panics
func (f *Factory) MustCreate(fieldGenFuncs ...FieldGenFunc) interface{} {
i, err := f.Create(fieldGenFuncs...)
if err != nil {
panic(err)
}
return i
}
// WithGen returns a function that generates an array of field generators,
// each of which has embedded check for field is present in the object being created and can be set.
func WithGen(g GeneratorFunc, fields ...string) FieldGenFunc {
return func(sample reflect.Value) []fieldWithGen {
gens := []fieldWithGen{}
elem := sample.Elem()
typ := elem.Type()
for _, fieldName := range fields {
sField, ok := typ.FieldByName(fieldName)
if !ok {
panic(fmt.Errorf("field %q not found in %s", fieldName, typ.Name()))
}
// check that field exists in generated model
field := elem.FieldByIndex(sField.Index)
if !field.IsValid() {
panic(fmt.Errorf("field %q is not valid in %s", fieldName, typ.Name()))
}
// and can be set
if !field.CanSet() {
panic(fmt.Errorf("field %q can not be set in %s", fieldName, typ.Name()))
}
gens = append(gens, fieldWithGen{&sField, g})
}
return gens
}
}
// check if value is zero value
func isZero(val reflect.Value) bool {
switch val.Kind() {
case reflect.Slice, reflect.Ptr, reflect.Map:
return val.IsNil()
default:
// otherwise allocate zero value
zero := reflect.Zero(val.Type())
// and compare
return val.Interface() == zero.Interface()
}
}
// protoGens takes a proto object and decomposes it into slice of field generators
// for each proto object field that has non-zero value.
func protoGens(proto interface{}) (fieldGenFuncs []FieldGenFunc) {
typ := reflect.TypeOf(proto)
// if proto object is non-zero type,
// walk object fields and create field generator for each field with non-zero value
val := reflect.ValueOf(proto)
for i := 0; i < typ.NumField(); i++ {
sField := typ.Field(i)
// skip unexported fields. from godoc:
// PkgPath is the package path that qualifies a lower case (unexported)
// field name. It is empty for upper case (exported) field names.
if sField.PkgPath != "" {
continue
}
if fVal := val.Field(i); !isZero(fVal) {
iVal := fVal.Interface()
fGen := Use(iVal).For(sField.Name)
if fieldGenFuncs != nil {
fieldGenFuncs = append(fieldGenFuncs, fGen)
} else {
fieldGenFuncs = []FieldGenFunc{fGen}
}
}
}
return
}
// NewFactory is factory constructor
func NewFactory(proto interface{}, fieldGenFuncs ...FieldGenFunc) *Factory {
typ := reflect.TypeOf(proto)
if protogens := protoGens(proto); len(protogens) > 0 {
// prepend field generators with proto generators if there are some
fieldGenFuncs = append(protogens, fieldGenFuncs...)
}
// sample is used to validate during the factory construction process that all
// provided fields exist in a given interface and can be set.
sample := reflect.New(typ)
fieldGens := make([]fieldWithGen, 0, len(fieldGenFuncs))
// create field generators
for _, makeFieldGen := range fieldGenFuncs {
fieldGens = append(fieldGens, makeFieldGen(sample)...)
}
return &Factory{typ: typ, fieldGens: fieldGens}
}