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 }