From 180f71e625073e210314dc8ca83dfbbbd0096dfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20Wyszy=C5=84ski?= Date: Mon, 3 Jun 2024 06:29:40 +0200 Subject: [PATCH] fix: indirection through nil pointer to embedded struct (#211) The provided test `TestUnmarshallToEmbeddedNoData` shows that the decoder panics when `setDefaults` encounters a nil pointer to an embedded struct. The fix replaces `FieldByName` with `FieldByIndexErr` to catch this kind of situation and continue with the next field. --- decoder.go | 17 ++++++++++--- decoder_test.go | 68 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 4 deletions(-) diff --git a/decoder.go b/decoder.go index 26f2b38..ed85641 100644 --- a/decoder.go +++ b/decoder.go @@ -104,6 +104,15 @@ func (d *Decoder) setDefaults(t reflect.Type, v reflect.Value) MultiError { errs := MultiError{} + if v.Type().Kind() == reflect.Struct { + for i := 0; i < v.NumField(); i++ { + field := v.Field(i) + if field.Type().Kind() == reflect.Ptr && field.IsNil() && v.Type().Field(i).Anonymous { + field.Set(reflect.New(field.Type().Elem())) + } + } + } + for _, f := range struc.fields { vCurrent := v.FieldByName(f.name) @@ -121,7 +130,7 @@ func (d *Decoder) setDefaults(t reflect.Type, v reflect.Value) MultiError { } else if f.typ.Kind() == reflect.Slice { vals := strings.Split(f.defaultValue, "|") - //check if slice has one of the supported types for defaults + // check if slice has one of the supported types for defaults if _, ok := builtinConverters[f.typ.Elem().Kind()]; !ok { errs.merge(MultiError{"default-" + f.name: errors.New("default option is supported only on: bool, float variants, string, unit variants types or their corresponding pointers or slices")}) continue @@ -129,7 +138,7 @@ func (d *Decoder) setDefaults(t reflect.Type, v reflect.Value) MultiError { defaultSlice := reflect.MakeSlice(f.typ, 0, cap(vals)) for _, val := range vals { - //this check is to handle if the wrong value is provided + // this check is to handle if the wrong value is provided convertedVal := builtinConverters[f.typ.Elem().Kind()](val) if !convertedVal.IsValid() { errs.merge(MultiError{"default-" + f.name: fmt.Errorf("failed setting default: %s is not compatible with field %s type", val, f.name)}) @@ -145,12 +154,12 @@ func (d *Decoder) setDefaults(t reflect.Type, v reflect.Value) MultiError { errs.merge(MultiError{"default-" + f.name: errors.New("default option is supported only on: bool, float variants, string, unit variants types or their corresponding pointers or slices")}) } - //this check is to handle if the wrong value is provided + // this check is to handle if the wrong value is provided if convertedVal := convertPointer(t1.Kind(), f.defaultValue); convertedVal.IsValid() { vCurrent.Set(convertedVal) } } else { - //this check is to handle if the wrong value is provided + // this check is to handle if the wrong value is provided if convertedVal := builtinConverters[f.typ.Kind()](f.defaultValue); convertedVal.IsValid() { vCurrent.Set(builtinConverters[f.typ.Kind()](f.defaultValue)) } diff --git a/decoder_test.go b/decoder_test.go index 6f71869..4f3b72a 100644 --- a/decoder_test.go +++ b/decoder_test.go @@ -2057,6 +2057,74 @@ func TestUnmashalPointerToEmbedded(t *testing.T) { } } +type S24 struct { + F1 string `schema:"F1"` +} + +type S24e struct { + *S24 + F2 string `schema:"F2"` +} + +func TestUnmarshallToEmbeddedNoData(t *testing.T) { + data := map[string][]string{ + "F3": {"raw a"}, + } + + s := &S24e{} + + decoder := NewDecoder() + err := decoder.Decode(s, data); + + expectedErr := `schema: invalid path "F3"` + if err.Error() != expectedErr { + t.Fatalf("got %q, want %q", err, expectedErr) + } +} +type S25ee struct { + F3 string `schema:"F3"` +} + +type S25e struct { + S25ee + F2 string `schema:"F2"` +} + +type S25 struct { + S25e + F1 string `schema:"F1"` +} + +func TestDoubleEmbedded(t *testing.T){ + data := map[string][]string{ + "F1": {"raw a"}, + "F2": {"raw b"}, + "F3": {"raw c"}, + } + + + s := S25{} + decoder := NewDecoder() + + if err := decoder.Decode(&s, data); err != nil { + t.Fatal("Error while decoding:", err) + } + + expected := S25{ + F1: "raw a", + S25e: S25e{ + F2: "raw b", + S25ee: S25ee{ + F3: "raw c", + }, + }, + } + if !reflect.DeepEqual(expected, s) { + t.Errorf("Expected %v errors, got %v", expected, s) + } + +} + func TestDefaultValuesAreSet(t *testing.T) { type N struct { S1 string `schema:"s1,default:test1"`