Skip to content

Commit

Permalink
fix: indirection through nil pointer to embedded struct (#211)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
morus12 committed Jun 3, 2024
1 parent a377fd6 commit 180f71e
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 4 deletions.
17 changes: 13 additions & 4 deletions decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -121,15 +130,15 @@ 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
}

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)})
Expand All @@ -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))
}
Expand Down
68 changes: 68 additions & 0 deletions decoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down

0 comments on commit 180f71e

Please sign in to comment.