Create Decoder struct, start work on Metadata
diff --git a/error.go b/error.go new file mode 100644 index 0000000..3460799 --- /dev/null +++ b/error.go
@@ -0,0 +1,32 @@ +package mapstructure + +import ( + "fmt" + "strings" +) + +// Error implements the error interface and can represents multiple +// errors that occur in the course of a single decode. +type Error struct { + Errors []string +} + +func (e *Error) Error() string { + points := make([]string, len(e.Errors)) + for i, err := range e.Errors { + points[i] = fmt.Sprintf("* %s", err) + } + + return fmt.Sprintf( + "%d error(s) decoding:\n\n%s", + len(e.Errors), strings.Join(points, "\n")) +} + +func appendErrors(errors []string, err error) []string { + switch e := err.(type) { + case *Error: + return append(errors, e.Errors...) + default: + return append(errors, e.Error()) + } +}
diff --git a/mapstructure.go b/mapstructure.go index 5c9c196..d2cd7a3 100644 --- a/mapstructure.go +++ b/mapstructure.go
@@ -14,41 +14,81 @@ "strings" ) -// Error implements the error interface and can represents multiple -// errors that occur in the course of a single decode. -type Error struct { - Errors []string +// DecoderConfig is the configuration that is used to create a new decoder +// and allows customization of various aspects of decoding. +type DecoderConfig struct { + // Metadata is the struct that will contain extra metadata about + // the decoding. If this is nil, then no metadata will be tracked. + Metadata *Metadata + + // Result is a pointer to the struct that will contain the decoded + // value. + Result interface{} } -func (e *Error) Error() string { - points := make([]string, len(e.Errors)) - for i, err := range e.Errors { - points[i] = fmt.Sprintf("* %s", err) - } +// A Decoder takes a raw interface value and turns it into structured +// data, keeping track of rich error information along the way in case +// anything goes wrong. +type Decoder struct { + config *DecoderConfig +} - return fmt.Sprintf( - "%d error(s) decoding:\n\n%s", - len(e.Errors), strings.Join(points, "\n")) +// Metadata contains information about decoding a structure that +// is tedious or difficult to get otherwise. +type Metadata struct { + // Keys are the keys of the structure which were successfully decoded + Keys []string + + // Unused is a slice of keys that were found in the raw value but + // weren't decoded since there was no matching field in the result interface + Unused []string } // Decode takes a map and uses reflection to convert it into the // given Go native structure. val must be a pointer to a struct. func Decode(m interface{}, rawVal interface{}) error { - val := reflect.ValueOf(rawVal) + config := &DecoderConfig{ + Metadata: nil, + Result: rawVal, + } + + decoder, err := NewDecoder(config) + if err != nil { + return err + } + + return decoder.Decode(m) +} + +// NewDecoder returns a new decoder for the given configuration. Once +// a decoder has been returned, the same configuration must not be used +// again. +func NewDecoder(config *DecoderConfig) (*Decoder, error) { + val := reflect.ValueOf(config.Result) if val.Kind() != reflect.Ptr { - return errors.New("val must be a pointer") + return nil, errors.New("result must be a pointer") } val = val.Elem() if !val.CanAddr() { - return errors.New("val must be addressable (a pointer)") + return nil, errors.New("result must be addressable (a pointer)") } - return decode("", m, val) + result := &Decoder{ + config: config, + } + + return result, nil +} + +// Decode decodes the given raw interface to the target pointer specified +// by the configuration. +func (d *Decoder) Decode(raw interface{}) error { + return d.decode("", raw, reflect.ValueOf(d.config.Result).Elem()) } // Decodes an unknown data type into a specific reflection value. -func decode(name string, data interface{}, val reflect.Value) error { +func (d *Decoder) decode(name string, data interface{}, val reflect.Value) error { dataVal := reflect.ValueOf(data) if !dataVal.IsValid() { // If the data value is invalid, then we just set the value @@ -72,17 +112,17 @@ case reflect.Interface: fallthrough case reflect.String: - return decodeBasic(name, data, val) + return d.decodeBasic(name, data, val) case reflect.Int: fallthrough case reflect.Uint: - return decodeInt(name, data, val) + return d.decodeInt(name, data, val) case reflect.Struct: - return decodeStruct(name, data, val) + return d.decodeStruct(name, data, val) case reflect.Map: - return decodeMap(name, data, val) + return d.decodeMap(name, data, val) case reflect.Slice: - return decodeSlice(name, data, val) + return d.decodeSlice(name, data, val) } // If we reached this point then we weren't able to decode it @@ -91,7 +131,7 @@ // This decodes a basic type (bool, int, string, etc.) and sets the // value to "data" of that type. -func decodeBasic(name string, data interface{}, val reflect.Value) error { +func (d *Decoder) decodeBasic(name string, data interface{}, val reflect.Value) error { dataVal := reflect.ValueOf(data) dataValType := dataVal.Type() if !dataValType.AssignableTo(val.Type()) { @@ -104,7 +144,7 @@ return nil } -func decodeInt(name string, data interface{}, val reflect.Value) error { +func (d *Decoder) decodeInt(name string, data interface{}, val reflect.Value) error { dataVal := reflect.ValueOf(data) dataKind := dataVal.Kind() if dataKind >= reflect.Int && dataKind <= reflect.Int64 { @@ -152,7 +192,7 @@ return nil } -func decodeMap(name string, data interface{}, val reflect.Value) error { +func (d *Decoder) decodeMap(name string, data interface{}, val reflect.Value) error { dataVal := reflect.Indirect(reflect.ValueOf(data)) if dataVal.Kind() != reflect.Map { return fmt.Errorf("'%s' expected a map, got '%s'", name, dataVal.Kind()) @@ -174,7 +214,7 @@ // First decode the key into the proper type currentKey := reflect.Indirect(reflect.New(valKeyType)) - if err := decode(fieldName, k.Interface(), currentKey); err != nil { + if err := d.decode(fieldName, k.Interface(), currentKey); err != nil { errors = appendErrors(errors, err) continue } @@ -182,7 +222,7 @@ // Next decode the data into the proper type v := dataVal.MapIndex(k).Interface() currentVal := reflect.Indirect(reflect.New(valElemType)) - if err := decode(fieldName, v, currentVal); err != nil { + if err := d.decode(fieldName, v, currentVal); err != nil { errors = appendErrors(errors, err) continue } @@ -201,7 +241,7 @@ return nil } -func decodeSlice(name string, data interface{}, val reflect.Value) error { +func (d *Decoder) decodeSlice(name string, data interface{}, val reflect.Value) error { dataVal := reflect.Indirect(reflect.ValueOf(data)) dataValKind := dataVal.Kind() if dataValKind != reflect.Array && dataValKind != reflect.Slice { @@ -224,7 +264,7 @@ currentField := valSlice.Index(i) fieldName := fmt.Sprintf("%s[%d]", name, i) - if err := decode(fieldName, currentData, currentField); err != nil { + if err := d.decode(fieldName, currentData, currentField); err != nil { errors = appendErrors(errors, err) } } @@ -240,7 +280,7 @@ return nil } -func decodeStruct(name string, data interface{}, val reflect.Value) error { +func (d *Decoder) decodeStruct(name string, data interface{}, val reflect.Value) error { dataVal := reflect.Indirect(reflect.ValueOf(data)) dataValKind := dataVal.Kind() if dataValKind != reflect.Map { @@ -304,7 +344,7 @@ fieldName = fmt.Sprintf("%s.%s", name, fieldName) } - if err := decode(fieldName, rawMapVal.Interface(), field); err != nil { + if err := d.decode(fieldName, rawMapVal.Interface(), field); err != nil { errors = appendErrors(errors, err) } } @@ -315,12 +355,3 @@ return nil } - -func appendErrors(errors []string, err error) []string { - switch e := err.(type) { - case *Error: - return append(errors, e.Errors...) - default: - return append(errors, e.Error()) - } -}
diff --git a/mapstructure_test.go b/mapstructure_test.go index d419e7d..4d664ed 100644 --- a/mapstructure_test.go +++ b/mapstructure_test.go
@@ -351,7 +351,7 @@ t.Fatal("error should exist") } - if err.Error() != "val must be a pointer" { + if err.Error() != "result must be a pointer" { t.Errorf("got unexpected error: %s", err) } }