Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ErrorUnset option to DecoderConfig and Unset array to Metddata #225

Merged
merged 1 commit into from Apr 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
37 changes: 36 additions & 1 deletion mapstructure.go
Expand Up @@ -200,6 +200,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 @@ -264,6 +269,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 @@ -355,6 +365,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 @@ -1225,6 +1239,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 @@ -1329,7 +1344,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 @@ -1389,6 +1405,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 @@ -1403,6 +1430,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 @@ -1225,6 +1225,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 @@ -2192,6 +2216,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