Skip to content

Commit

Permalink
Merge pull request #225 from SaschaRoland/unset-fields
Browse files Browse the repository at this point in the history
Add ErrorUnset option to DecoderConfig and Unset array to Metddata
  • Loading branch information
mitchellh committed Apr 20, 2022
2 parents 17e49ec + d91ccce commit 8385cfa
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 1 deletion.
37 changes: 36 additions & 1 deletion mapstructure.go
Expand Up @@ -215,6 +215,11 @@ type DecoderConfig struct {
// (extra keys).
ErrorUnused bool

// If ErrorUnset is true, then it is an error for there to exist
// fields in the result that were not set in the decoding process
// (extra fields).
ErrorUnset bool

// ZeroFields, if set to true, will zero fields before writing them.
// For example, a map will be emptied before decoded values are put in
// it. If this is false, a map will be merged.
Expand Down Expand Up @@ -288,6 +293,11 @@ type Metadata struct {
// 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

// Unset is a slice of field names that were found in the result interface
// but weren't set in the decoding process since there was no matching value
// in the input
Unset []string
}

// Decode takes an input structure and uses reflection to translate it to
Expand Down Expand Up @@ -379,6 +389,10 @@ func NewDecoder(config *DecoderConfig) (*Decoder, error) {
if config.Metadata.Unused == nil {
config.Metadata.Unused = make([]string, 0)
}

if config.Metadata.Unset == nil {
config.Metadata.Unset = make([]string, 0)
}
}

if config.TagName == "" {
Expand Down Expand Up @@ -1260,6 +1274,7 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e
dataValKeysUnused[dataValKey.Interface()] = struct{}{}
}

targetValKeysUnused := make(map[interface{}]struct{})
errors := make([]string, 0)

// This slice will keep track of all the structs we'll be decoding.
Expand Down Expand Up @@ -1364,7 +1379,8 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e

if !rawMapVal.IsValid() {
// There was no matching key in the map for the value in
// the struct. Just ignore.
// the struct. Remember it for potential errors and metadata.
targetValKeysUnused[fieldName] = struct{}{}
continue
}
}
Expand Down Expand Up @@ -1424,6 +1440,17 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e
errors = appendErrors(errors, err)
}

if d.config.ErrorUnset && len(targetValKeysUnused) > 0 {
keys := make([]string, 0, len(targetValKeysUnused))
for rawKey := range targetValKeysUnused {
keys = append(keys, rawKey.(string))
}
sort.Strings(keys)

err := fmt.Errorf("'%s' has unset fields: %s", name, strings.Join(keys, ", "))
errors = appendErrors(errors, err)
}

if len(errors) > 0 {
return &Error{errors}
}
Expand All @@ -1438,6 +1465,14 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e

d.config.Metadata.Unused = append(d.config.Metadata.Unused, key)
}
for rawKey := range targetValKeysUnused {
key := rawKey.(string)
if name != "" {
key = name + "." + key
}

d.config.Metadata.Unset = append(d.config.Metadata.Unset, key)
}
}

return nil
Expand Down
32 changes: 32 additions & 0 deletions mapstructure_test.go
Expand Up @@ -1376,6 +1376,30 @@ func TestDecoder_ErrorUnused_NotSetable(t *testing.T) {
t.Fatal("expected error")
}
}
func TestDecoder_ErrorUnset(t *testing.T) {
t.Parallel()

input := map[string]interface{}{
"vstring": "hello",
"foo": "bar",
}

var result Basic
config := &DecoderConfig{
ErrorUnset: true,
Result: &result,
}

decoder, err := NewDecoder(config)
if err != nil {
t.Fatalf("err: %s", err)
}

err = decoder.Decode(input)
if err == nil {
t.Fatal("expected error")
}
}

func TestMap(t *testing.T) {
t.Parallel()
Expand Down Expand Up @@ -2345,6 +2369,14 @@ func TestMetadata(t *testing.T) {
if !reflect.DeepEqual(md.Unused, expectedUnused) {
t.Fatalf("bad unused: %#v", md.Unused)
}

expectedUnset := []string{
"Vbar.Vbool", "Vbar.Vdata", "Vbar.Vextra", "Vbar.Vfloat", "Vbar.Vint",
"Vbar.VjsonFloat", "Vbar.VjsonInt", "Vbar.VjsonNumber"}
sort.Strings(md.Unset)
if !reflect.DeepEqual(md.Unset, expectedUnset) {
t.Fatalf("bad unset: %#v", md.Unset)
}
}

func TestMetadata_Embedded(t *testing.T) {
Expand Down

0 comments on commit 8385cfa

Please sign in to comment.