unpack.go (160 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 ucfg import "reflect" // Unpacker type used by Unpack to allow types to implement custom configuration // unpacking. type Unpacker interface { // Unpack is called if a setting of field has a type implementing Unpacker. // // The interface{} value passed to Unpack can be of type: bool, int64, uint64, // float64, string, []interface{} or map[string]interface{}. Unpack(interface{}) error } // BoolUnpacker interface specializes the Unpacker interface // by casting values to bool when calling Unpack. type BoolUnpacker interface { Unpack(b bool) error } // IntUnpacker interface specializes the Unpacker interface // by casting values to int64 when calling Unpack. type IntUnpacker interface { Unpack(i int64) error } // UintUnpacker interface specializes the Unpacker interface // by casting values to uint64 when calling Unpack. type UintUnpacker interface { Unpack(u uint64) error } // FloatUnpacker interface specializes the Unpacker interface // by casting values to float64 when calling Unpack. type FloatUnpacker interface { Unpack(f float64) error } // StringUnpacker interface specializes the Unpacker interface // by casting values to string when calling Unpack. type StringUnpacker interface { Unpack(s string) error } // ConfigUnpacker interface specializes the Unpacker interface // by passing the the *Config object directly instead of // transforming the *Config object into map[string]interface{}. type ConfigUnpacker interface { Unpack(c *Config) error } var ( // unpacker interface types tUnpacker = reflect.TypeOf((*Unpacker)(nil)).Elem() tBoolUnpacker = reflect.TypeOf((*BoolUnpacker)(nil)).Elem() tIntUnpacker = reflect.TypeOf((*IntUnpacker)(nil)).Elem() tUintUnpacker = reflect.TypeOf((*UintUnpacker)(nil)).Elem() tFloatUnpacker = reflect.TypeOf((*FloatUnpacker)(nil)).Elem() tStringUnpacker = reflect.TypeOf((*StringUnpacker)(nil)).Elem() tConfigUnpacker = reflect.TypeOf((*ConfigUnpacker)(nil)).Elem() tUnpackers = [...]reflect.Type{ tUnpacker, tBoolUnpacker, tIntUnpacker, tUintUnpacker, tFloatUnpacker, tStringUnpacker, tConfigUnpacker, } ) // valueIsUnpacker checks if v implements the Unpacker interface. // If there exists a pointer to v, the pointer to v is also tested. func valueIsUnpacker(v reflect.Value) (reflect.Value, bool) { for { if implementsUnpacker(v.Type()) { return v, true } if !v.CanAddr() { break } v = v.Addr() } return reflect.Value{}, false } func typeIsUnpacker(t reflect.Type) (reflect.Value, bool) { if implementsUnpacker(t) { return reflect.New(t).Elem(), true } if implementsUnpacker(reflect.PtrTo(t)) { return reflect.New(t), true } return reflect.Value{}, false } func implementsUnpacker(t reflect.Type) bool { // ucfg.Config or structures that can be casted to ucfg.Config are not // Unpackers. if tConfig.ConvertibleTo(chaseTypePointers(t)) { return false } for _, tUnpack := range tUnpackers { if t.Implements(tUnpack) { return true } } if t.NumMethod() == 0 { return false } // test if object has 'Unpack' method method, ok := t.MethodByName("Unpack") if !ok { return false } // check method input and output parameters to match the ConfigUnpacker interface: // func (to *T) Unpack(cfg *TConfig) error // with T being the method receiver (input paramter 0) // and TConfig being the aliased config type to convert to (input parameter 1) paramCountCheck := method.Type.NumIn() == 2 && method.Type.NumOut() == 1 if !paramCountCheck { return false } if !method.Type.Out(0).Implements(tError) { // return variable is not compatible to `error` type return false } // method receiver is known, check config parameters being compatible tIn := method.Type.In(1) return tConfig.ConvertibleTo(tIn) || tConfigPtr.ConvertibleTo(tIn) } func unpackWith(opts *options, v reflect.Value, with value) Error { // short circuit nil values if isNil(with) { return nil } ctx := with.Context() meta := with.meta() var err error value := v.Interface() switch u := value.(type) { case Unpacker: var reified interface{} if reified, err = with.reify(opts); err == nil { err = u.Unpack(reified) } case BoolUnpacker: var b bool if b, err = with.toBool(opts); err == nil { err = u.Unpack(b) } case IntUnpacker: var n int64 if n, err = with.toInt(opts); err == nil { err = u.Unpack(n) } case UintUnpacker: var n uint64 if n, err = with.toUint(opts); err == nil { err = u.Unpack(n) } case FloatUnpacker: var f float64 if f, err = with.toFloat(opts); err == nil { err = u.Unpack(f) } case StringUnpacker: var s string if s, err = with.toString(opts); err == nil { err = u.Unpack(s) } case ConfigUnpacker: var c *Config if c, err = with.toConfig(opts); err == nil { err = u.Unpack(c) } default: var c *Config if c, err = with.toConfig(opts); err == nil { err = reflectUnpackWithConfig(v, c) } } if err != nil { return raisePathErr(err, meta, "", ctx.path(".")) } return nil } func reflectUnpackWithConfig(v reflect.Value, c *Config) error { method, _ := v.Type().MethodByName("Unpack") tIn := method.Type.In(1) var rc reflect.Value switch { case tConfig.ConvertibleTo(tIn): rc = reflect.ValueOf(*c) case tConfigPtr.ConvertibleTo(tIn): rc = reflect.ValueOf(c) } results := method.Func.Call([]reflect.Value{v, rc.Convert(tIn)}) ifc := results[0].Convert(tError).Interface() if ifc == nil { return nil } return ifc.(error) }