330 lines
7.6 KiB
Go
330 lines
7.6 KiB
Go
package stalecucumber
|
|
|
|
import "reflect"
|
|
import "fmt"
|
|
import "errors"
|
|
import "strings"
|
|
import "math/big"
|
|
|
|
const PICKLE_TAG = "pickle"
|
|
|
|
type UnpackingError struct {
|
|
Source interface{}
|
|
Destination reflect.Value
|
|
Err error
|
|
}
|
|
|
|
/*
|
|
This type is returned when a call to From() fails.
|
|
Setting "AllowMissingFields" and "AllowMismatchedFields"
|
|
on the result of "UnpackInto" controls if this error is
|
|
returned or not.
|
|
*/
|
|
func (ue UnpackingError) Error() string {
|
|
var dv string
|
|
var dt string
|
|
k := ue.Destination.Kind()
|
|
switch k {
|
|
case reflect.Ptr:
|
|
dt = fmt.Sprintf("%s", ue.Destination.Type().Elem())
|
|
if !ue.Destination.IsNil() {
|
|
dv = fmt.Sprintf("%v", ue.Destination.Elem().Interface())
|
|
} else {
|
|
dv = "nil"
|
|
|
|
}
|
|
case reflect.Invalid:
|
|
dv = "invalid"
|
|
dt = dv
|
|
default:
|
|
dv = fmt.Sprintf("%v", ue.Destination.Interface())
|
|
dt = fmt.Sprintf("%s", ue.Destination.Type())
|
|
}
|
|
|
|
return fmt.Sprintf("Error unpacking %v(%T) into %s(%s):%v",
|
|
ue.Source,
|
|
ue.Source,
|
|
dv,
|
|
dt,
|
|
ue.Err)
|
|
}
|
|
|
|
var ErrNilPointer = errors.New("Destination cannot be a nil pointer")
|
|
var ErrNotPointer = errors.New("Destination must be a pointer type")
|
|
var ErrTargetTypeNotPointer = errors.New("Target type must be a pointer to unpack this value")
|
|
var ErrTargetTypeOverflow = errors.New("Value overflows target type")
|
|
var ErrTargetTypeMismatch = errors.New("Target type does not match source type")
|
|
|
|
type unpacker struct {
|
|
dest reflect.Value
|
|
AllowMissingFields bool
|
|
AllowMismatchedFields bool
|
|
}
|
|
|
|
func UnpackInto(dest interface{}) unpacker {
|
|
return unpacker{dest: reflect.ValueOf(dest),
|
|
AllowMissingFields: true,
|
|
AllowMismatchedFields: false}
|
|
}
|
|
|
|
func (u unpacker) From(srcI interface{}, err error) error {
|
|
//Check if an error occurred
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return u.from(srcI)
|
|
}
|
|
|
|
func (u unpacker) from(srcI interface{}) error {
|
|
|
|
//Get the value of the destination
|
|
v := u.dest
|
|
|
|
//The destination must always be a pointer
|
|
if v.Kind() != reflect.Ptr {
|
|
return UnpackingError{Source: srcI,
|
|
Destination: u.dest,
|
|
Err: ErrNotPointer}
|
|
}
|
|
|
|
//The destination can never be nil
|
|
if v.IsNil() {
|
|
return UnpackingError{Source: srcI,
|
|
Destination: u.dest,
|
|
Err: ErrNilPointer}
|
|
|
|
}
|
|
|
|
//Indirect the destination. This gets the actual
|
|
//value pointed at
|
|
vIndirect := v
|
|
for vIndirect.Kind() == reflect.Ptr {
|
|
if vIndirect.IsNil() {
|
|
vIndirect.Set(reflect.New(vIndirect.Type().Elem()))
|
|
}
|
|
vIndirect = vIndirect.Elem()
|
|
}
|
|
|
|
//Check the input against known types
|
|
switch s := srcI.(type) {
|
|
default:
|
|
return UnpackingError{Source: srcI,
|
|
Destination: u.dest,
|
|
Err: errors.New("Unknown source type")}
|
|
case PickleNone:
|
|
vElem := v.Elem()
|
|
for vElem.Kind() == reflect.Ptr {
|
|
next := vElem.Elem()
|
|
if next.Kind() == reflect.Ptr {
|
|
vElem = next
|
|
continue
|
|
}
|
|
|
|
if vElem.CanSet() {
|
|
vElem.Set(reflect.Zero(vElem.Type()))
|
|
return nil
|
|
}
|
|
}
|
|
|
|
return UnpackingError{Source: srcI,
|
|
Destination: u.dest,
|
|
Err: ErrTargetTypeNotPointer}
|
|
|
|
case int64:
|
|
switch vIndirect.Kind() {
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int64, reflect.Int32:
|
|
if vIndirect.OverflowInt(s) {
|
|
return UnpackingError{Source: srcI,
|
|
Destination: u.dest,
|
|
Err: ErrTargetTypeOverflow}
|
|
}
|
|
vIndirect.SetInt(s)
|
|
return nil
|
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
if s < 0 || vIndirect.OverflowUint(uint64(s)) {
|
|
return UnpackingError{Source: srcI,
|
|
Destination: u.dest,
|
|
Err: ErrTargetTypeOverflow}
|
|
}
|
|
|
|
vIndirect.SetUint(uint64(s))
|
|
return nil
|
|
}
|
|
|
|
dstBig, ok := vIndirect.Addr().Interface().(*big.Int)
|
|
if ok {
|
|
dstBig.SetInt64(s)
|
|
return nil
|
|
}
|
|
|
|
case string:
|
|
switch vIndirect.Kind() {
|
|
case reflect.String:
|
|
vIndirect.SetString(s)
|
|
return nil
|
|
}
|
|
case bool:
|
|
switch vIndirect.Kind() {
|
|
case reflect.Bool:
|
|
vIndirect.SetBool(s)
|
|
return nil
|
|
}
|
|
case float64:
|
|
switch vIndirect.Kind() {
|
|
case reflect.Float32, reflect.Float64:
|
|
vIndirect.SetFloat(s)
|
|
return nil
|
|
}
|
|
case *big.Int:
|
|
dstBig, ok := vIndirect.Addr().Interface().(*big.Int)
|
|
if ok {
|
|
dstBig.Set(s)
|
|
return nil
|
|
}
|
|
|
|
if vi, err := Int(srcI, nil); err == nil {
|
|
return unpacker{dest: v,
|
|
AllowMismatchedFields: u.AllowMismatchedFields,
|
|
AllowMissingFields: u.AllowMissingFields}.From(vi, nil)
|
|
}
|
|
|
|
case []interface{}:
|
|
//Check that the destination is a slice
|
|
if vIndirect.Kind() != reflect.Slice {
|
|
return UnpackingError{Source: s,
|
|
Destination: u.dest,
|
|
Err: fmt.Errorf("Cannot unpack slice into destination")}
|
|
}
|
|
//Check for exact type match
|
|
if vIndirect.Type().Elem().Kind() == reflect.Interface {
|
|
vIndirect.Set(reflect.ValueOf(s))
|
|
return nil
|
|
}
|
|
|
|
//Build the value using reflection
|
|
var replacement reflect.Value
|
|
if vIndirect.IsNil() || vIndirect.Len() < len(s) {
|
|
replacement = reflect.MakeSlice(vIndirect.Type(),
|
|
len(s), len(s))
|
|
} else {
|
|
replacement = vIndirect.Slice(0, len(s))
|
|
}
|
|
|
|
for i, srcV := range s {
|
|
dstV := replacement.Index(i)
|
|
|
|
//Recurse to set the value
|
|
err := unpacker{dest: dstV.Addr(),
|
|
AllowMissingFields: u.AllowMissingFields,
|
|
AllowMismatchedFields: u.AllowMismatchedFields}.
|
|
From(srcV, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
vIndirect.Set(replacement)
|
|
return nil
|
|
|
|
case map[interface{}]interface{}:
|
|
//Check to see if the field is exactly
|
|
//of the type
|
|
if vIndirect.Kind() == reflect.Map {
|
|
dstT := vIndirect.Type()
|
|
if dstT.Key().Kind() == reflect.Interface &&
|
|
dstT.Elem().Kind() == reflect.Interface {
|
|
vIndirect.Set(reflect.ValueOf(s))
|
|
return nil
|
|
}
|
|
}
|
|
|
|
var src map[string]interface{}
|
|
var err error
|
|
src, err = DictString(srcI, err)
|
|
if err != nil {
|
|
return UnpackingError{Source: srcI,
|
|
Destination: u.dest,
|
|
Err: fmt.Errorf("Cannot unpack source into struct")}
|
|
}
|
|
|
|
if vIndirect.Kind() != reflect.Struct {
|
|
return UnpackingError{Source: src,
|
|
Destination: u.dest,
|
|
Err: fmt.Errorf("Cannot unpack into %v", v.Kind().String())}
|
|
|
|
}
|
|
|
|
var fieldByTag map[string]int
|
|
vIndirectType := reflect.TypeOf(vIndirect.Interface())
|
|
numFields := vIndirectType.NumField()
|
|
|
|
for i := 0; i != numFields; i++ {
|
|
fv := vIndirectType.Field(i)
|
|
tag := fv.Tag.Get(PICKLE_TAG)
|
|
|
|
if len(tag) != 0 {
|
|
if fieldByTag == nil {
|
|
fieldByTag = make(map[string]int)
|
|
}
|
|
fieldByTag[tag] = i
|
|
}
|
|
}
|
|
|
|
for k, kv := range src {
|
|
var fv reflect.Value
|
|
|
|
if fieldIndex, ok := fieldByTag[k]; ok {
|
|
fv = vIndirect.Field(fieldIndex)
|
|
} else {
|
|
//Try the name verbatim. This catches
|
|
//embedded fields as well
|
|
fv = vIndirect.FieldByName(k)
|
|
|
|
if !fv.IsValid() {
|
|
//Capitalize the first character. Structs
|
|
//do not export fields with a lower case
|
|
//first character
|
|
capk := strings.ToUpper(k[0:1]) + k[1:]
|
|
fv = vIndirect.FieldByName(capk)
|
|
}
|
|
}
|
|
|
|
if !fv.IsValid() || !fv.CanSet() {
|
|
if !u.AllowMissingFields {
|
|
return UnpackingError{Source: src,
|
|
Destination: u.dest,
|
|
Err: fmt.Errorf("Cannot find field for key %q", k)}
|
|
}
|
|
continue
|
|
}
|
|
|
|
err := unpacker{dest: fv.Addr(),
|
|
AllowMismatchedFields: u.AllowMismatchedFields,
|
|
AllowMissingFields: u.AllowMissingFields}.from(kv)
|
|
|
|
if err != nil {
|
|
if u.AllowMismatchedFields {
|
|
if unpackingError, ok := err.(UnpackingError); ok {
|
|
switch unpackingError.Err {
|
|
case ErrTargetTypeOverflow,
|
|
ErrTargetTypeNotPointer,
|
|
ErrTargetTypeMismatch:
|
|
|
|
fv.Set(reflect.Zero(fv.Type()))
|
|
continue
|
|
}
|
|
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
return UnpackingError{Source: srcI,
|
|
Destination: u.dest,
|
|
Err: ErrTargetTypeMismatch}
|
|
}
|