gotype/fold.go (195 lines of code) (raw):
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package gotype
import (
"reflect"
structform "github.com/elastic/go-structform"
)
type foldFn func(c *foldContext, v interface{}) error
type reFoldFn func(c *foldContext, v reflect.Value) error
type visitor interface {
structform.ExtVisitor
}
type Iterator struct {
ctx foldContext
}
type Folder interface {
Fold(structform.ExtVisitor) error
}
// IsZeroer interface allows custom types to be reported as empty.
// If the `omitempty` struct tag option is set and the custom type implements
// IsZero(), which returns true, then the field will not be reported.
type IsZeroer interface {
IsZero() bool
}
type foldContext struct {
visitor
userReg map[reflect.Type]reFoldFn
reg *typeFoldRegistry
opts options
}
func Fold(v interface{}, vs structform.Visitor, opts ...FoldOption) error {
if it, err := NewIterator(vs, opts...); err == nil {
return it.Fold(v)
}
return nil
}
func NewIterator(vs structform.Visitor, opts ...FoldOption) (*Iterator, error) {
reg := newTypeFoldRegistry()
O, err := applyFoldOpts(opts)
if err != nil {
return nil, err
}
var userReg map[reflect.Type]reFoldFn
if O.foldFns != nil {
userReg = map[reflect.Type]reFoldFn{}
for typ, folder := range O.foldFns {
reg.set(typ, folder)
userReg[typ] = folder
}
}
it := &Iterator{
ctx: foldContext{
visitor: structform.EnsureExtVisitor(vs).(visitor),
userReg: userReg,
reg: reg,
opts: options{
tag: "struct",
},
},
}
return it, nil
}
func (i *Iterator) Fold(v interface{}) error {
return foldInterfaceValue(&i.ctx, v)
}
func foldInterfaceValue(C *foldContext, v interface{}) error {
if C.userReg != nil {
t := reflect.TypeOf(v)
if f := C.userReg[t]; f != nil {
return f(C, reflect.ValueOf(v))
}
}
if f := getFoldGoTypes(v); f != nil {
return f(C, v)
}
if f, ok := v.(Folder); ok {
return f.Fold(C.visitor)
}
if tmp, f := getFoldConvert(v); f != nil {
return f(C, tmp)
}
return foldAnyReflect(C, reflect.ValueOf(v))
}
func getFoldConvert(v interface{}) (interface{}, foldFn) {
t := reflect.TypeOf(v)
cast := false
switch t.Kind() {
case reflect.Map:
if cast = t.Name() != ""; cast {
mt := reflect.MapOf(t.Key(), t.Elem())
v = reflect.ValueOf(v).Convert(mt).Interface()
}
case reflect.Slice:
if cast = t.Name() != ""; cast {
mt := reflect.SliceOf(t.Elem())
v = reflect.ValueOf(v).Convert(mt).Interface()
}
case reflect.Array:
if cast = t.Name() != ""; cast {
mt := reflect.ArrayOf(t.Len(), t.Elem())
v = reflect.ValueOf(v).Convert(mt).Interface()
}
}
return v, getFoldGoTypes(v)
}
func getFoldGoTypes(v interface{}) foldFn {
switch v.(type) {
case nil:
return foldNil
case bool:
return foldBool
case []bool:
return foldArrBool
case map[string]bool:
return foldMapBool
case int8:
return foldInt8
case int16:
return foldInt16
case int32:
return foldInt32
case int64:
return foldInt64
case int:
return foldInt
case []int8:
return foldArrInt8
case []int16:
return foldArrInt16
case []int32:
return foldArrInt32
case []int64:
return foldArrInt64
case []int:
return foldArrInt
case map[string]int8:
return foldMapInt8
case map[string]int16:
return foldMapInt16
case map[string]int32:
return foldMapInt32
case map[string]int64:
return foldMapInt64
case map[string]int:
return foldMapInt
/*
case byte:
return visitByte
*/
case uint8:
return foldUint8
case uint16:
return foldUint16
case uint32:
return foldUint32
case uint64:
return foldUint64
case uint:
return foldUint
case []byte:
return foldBytes
/*
case []uint8:
return visitArrUint8
*/
case []uint16:
return foldArrUint16
case []uint32:
return foldArrUint32
case []uint64:
return foldArrUint64
case []uint:
return foldArrUint
case map[string]uint8:
return foldMapUint8
case map[string]uint16:
return foldMapUint16
case map[string]uint32:
return foldMapUint32
case map[string]uint64:
return foldMapUint64
case map[string]uint:
return foldMapUint
case float32:
return foldFloat32
case float64:
return foldFloat64
case []float32:
return foldArrFloat32
case []float64:
return foldArrFloat64
case map[string]float32:
return foldMapFloat32
case map[string]float64:
return foldMapFloat64
case string:
return foldString
case []string:
return foldArrString
case map[string]string:
return foldMapString
case []interface{}:
return foldArrInterface
case map[string]interface{}:
return foldMapInterface
}
return nil
}