This repository has been archived by the owner on Jan 20, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathstructor.go
294 lines (274 loc) · 8.39 KB
/
structor.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
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
/*
Package structor contains interface and default implementation of EL
(expression language) evaluator which evaluates EL expressions within tags of
the struct, using optional additional struct as an extra context.
Basic idea is to use simple expression language within Go struct tags to
compute struct fields based on other fields or provided additional context.
Due usage of reflection and EL interpretation, this package is hardly suitable
for tasks, requiring high performance, but rather intended to be used during
application setup or in cases where high performance is not an ultimate goal.
See tests for examples of usage with xpath, regexp, goquery, encryption,
reading from files, shell invocation, etc.
*/
package structor
import (
"fmt"
"reflect"
multierror "github.com/hashicorp/go-multierror"
"github.com/nikolay-turpitko/structor/el"
"github.com/nikolay-turpitko/structor/funcs/use"
"github.com/nikolay-turpitko/structor/scanner"
)
// Evaluator is an interface of evaluator, which gets structure and extra
// context as input, iterates over `s`'s fields and evaluate expression tag on
// every field.
type Evaluator interface {
Eval(s, extra interface{}) error
}
// Interpreters is a map of tag names to el.Interpreters. Used to register
// different interpreters for different tag names.
//
// Only first tag name on the struct field is currently recognized and
// processed. So, only one EL expression per structure field, but different
// fields of the same structure can be processed by different interpreters.
type Interpreters map[string]el.Interpreter
// WholeTag constant can be used as tag name in the Interpreters to indicate
// that whole tag value should be passed to the interpreter.
//
// Registering interpreter to the whole tag value conflicts with any other
// usage of the struct's tag, but can be convenient to simplify complex EL
// expressions with quotes (regexp, for example).
//
// WholeTag interpreter is probed after all other registered interpreters.
const WholeTag = ""
// NewEvaluatorWithOptions returns Evaluator with desired settings.
//
// Only first tag with EL will be recognized and used (only one
// expression per struct field). Different fields of the same struct can be
// processed using different EL interpreters.
//
// scanner - is a scanner implementation to be used to scan tags.
// interpreters - is a map of registered tag names to EL interpreters.
// options - is an Options structure.
func NewEvaluatorWithOptions(
scanner scanner.Scanner,
interpreters Interpreters,
options Options) Evaluator {
if len(interpreters) == 0 {
panic("no interpreters registered")
}
return &evaluator{scanner, interpreters, options}
}
// NewEvaluator returns Evaluator with desired settings.
// It invokes NewEvaluatorWithOptions with default options.
func NewEvaluator(
scanner scanner.Scanner,
interpreters Interpreters) Evaluator {
return NewEvaluatorWithOptions(scanner, interpreters, Options{})
}
// NewDefaultEvaluator returns default Evaluator implementation. Default
// implementation uses tag "eval" for expressions and EL interpreter, based on
// `"text/template"`.
//
// funcs - custom functions, available for interpreter;
func NewDefaultEvaluator(funcs use.FuncMap) Evaluator {
return NewEvaluator(
scanner.Default,
Interpreters{
"eval": &el.DefaultInterpreter{Funcs: funcs},
})
}
// NewNonmutatingEvaluator creates Evaluator implementation which does not
// change original structure (does not save evaluated results) itself. Though,
// interpreters can change structures' fields as a side effect. In this case
// Evaluator can be used as a visitor of fields with tags for which it have
// registered interpreters. It will invoke registered interpreter for field
// with corresponded tag. Interpreter can then manipulate it's own state or
// el.Context. For example, it can store processing results into context's
// Extra field.
//
// See NewEvaluator() for additional information.
func NewNonmutatingEvaluator(
scanner scanner.Scanner,
interpreters Interpreters) Evaluator {
return NewEvaluatorWithOptions(scanner, interpreters, Options{NonMutating: true})
}
type evaluator struct {
scanner scanner.Scanner
interpreters Interpreters
options Options
}
// Options is an options to create Evaluator.
type Options struct {
// NonMutating creates non-mutating Evaluator, see NewNonmutatingEvaluator.
NonMutating bool
// EvalEmptyTags causes Evaluator to invoke Interpreter for fields with
// empty tags.
EvalEmptyTags bool
}
func (ev evaluator) Eval(s, extra interface{}) error {
v := reflect.ValueOf(s)
t := v.Type()
k := t.Kind()
prev := v
for v.IsValid() && (k == reflect.Interface || k == reflect.Ptr) {
prev = v
v = v.Elem()
if v.IsValid() {
t = v.Type()
k = t.Kind()
}
if prev == v {
break
}
}
if k != reflect.Struct || !v.CanSet() {
return fmt.Errorf("structor: %T: not a settable struct", s)
}
return multierror.Prefix(
ev.eval(
"",
nil,
reflect.ValueOf(s),
&el.Context{
Struct: s,
Extra: extra,
EvalExpr: ev.evalExpr,
LongName: fmt.Sprintf("%T", s),
}), "structor:")
}
func (ev evaluator) evalExpr(
intrprName, expr string,
ctx *el.Context) (interface{}, error) {
intrpr, ok := ev.interpreters[intrprName]
if !ok {
return nil, fmt.Errorf("unknown interpreter: %s", intrprName)
}
return intrpr.Execute(expr, ctx)
}
func (ev evaluator) eval(
expr string,
interpreter el.Interpreter,
v reflect.Value,
ctx *el.Context) (err error) {
// Note: only errors returned by recursive call should not be prefixed.
defer func() {
if r := recover(); r != nil {
var ok bool
if err, ok = r.(error); !ok {
err = multierror.Prefix(
fmt.Errorf("%v", r),
fmt.Sprintf("<<%s>>", ctx.LongName))
}
}
}()
if !v.IsValid() {
return nil
}
t := v.Type()
k := t.Kind()
v = tryUnseal(v)
elV, elT, elK := v, t, k
for elV.IsValid() && (elK == reflect.Interface || elK == reflect.Ptr) {
v, t, k = elV, elT, elK
elV = v.Elem()
if elV.IsValid() {
elT = elV.Type()
elK = elT.Kind()
}
if v == elV {
break
}
}
if elV.IsValid() {
elV = tryUnseal(elV)
elT = elV.Type()
elK = elT.Kind()
}
var merr *multierror.Error
var ctxSub interface{}
if (expr != "" || ev.options.EvalEmptyTags) && interpreter != nil {
ctx.Val = nil
if elV.IsValid() {
ctx.Val = elV.Interface()
}
if result, err := interpreter.Execute(expr, ctx); err != nil {
merr = multierror.Append(
merr,
multierror.Prefix(err, fmt.Sprintf("<<%s>>", ctx.LongName)))
} else {
ctxSub = result
if !ev.options.NonMutating {
if result == nil {
if v.IsValid() {
v.Set(reflect.Zero(t))
}
} else {
vnv := reflect.ValueOf(result)
if vnv.Type().ConvertibleTo(t) || elK != reflect.Struct {
// Try to convert, it may give a panic with suitable
// message.
v.Set(vnv.Convert(t))
}
}
}
}
}
switch elK {
case reflect.Slice, reflect.Array:
prevLongName := ctx.LongName
for i, l := 0, elV.Len(); i < l; i++ {
v := elV.Index(i)
ctx.Name = v.Type().Name()
ctx.LongName = fmt.Sprintf("%s[%d]", ctx.LongName, i)
ctx.Tags = nil
err := ev.eval("", nil, v, ctx)
ctx.LongName = prevLongName
merr = multierror.Append(merr, err)
}
case reflect.Struct:
ctx.Sub = ctxSub
prevLongName := ctx.LongName
for i, l := 0, elV.NumField(); i < l; i++ {
tf := elT.Field(i)
tags, err := ev.scanner.Tags(tf.Tag)
if err != nil {
merr = multierror.Append(
merr,
multierror.Prefix(err, fmt.Sprintf("<<%s>>", ctx.LongName)))
break
}
expr := ""
var interpreter el.Interpreter
for k, t := range tags {
if i, ok := ev.interpreters[k]; ok {
delete(tags, k)
expr, interpreter = t, i
}
}
if i, ok := ev.interpreters[WholeTag]; ok && interpreter == nil {
delete(tags, WholeTag)
expr, interpreter = string(tf.Tag), i
}
ctx.Name = tf.Name
ctx.LongName = fmt.Sprintf("%s.%s", ctx.LongName, tf.Name)
ctx.Tags = tags
v := elV.Field(i)
err = ev.eval(expr, interpreter, v, ctx)
ctx.LongName = prevLongName
merr = multierror.Append(merr, err)
}
case reflect.Map:
prevLongName := ctx.LongName
for _, key := range elV.MapKeys() {
v := elV.MapIndex(key)
ctx.Name = v.Type().Name()
ctx.LongName = fmt.Sprintf("%s[%v]", ctx.LongName, key)
ctx.Tags = nil
err := ev.eval("", nil, v, ctx)
ctx.LongName = prevLongName
merr = multierror.Append(merr, err)
}
}
return merr.ErrorOrNil()
}