Skip to content

Commit

Permalink
handle slices and cases when invalid default values are provided
Browse files Browse the repository at this point in the history
  • Loading branch information
zak905 committed Jan 18, 2024
1 parent a128c31 commit 62c85ed
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 45 deletions.
84 changes: 56 additions & 28 deletions converter.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,47 +147,75 @@ func convertUint64(value string) reflect.Value {
func convertPointer(k reflect.Kind, value string) reflect.Value {
switch k {
case boolType:
v := convertBool(value).Bool()
return reflect.ValueOf(&v)
if v := convertBool(value); v.IsValid() {
converted := v.Bool()
return reflect.ValueOf(&converted)
}
case float32Type:
v := float32(convertFloat32(value).Float())
return reflect.ValueOf(&v)
if v := convertFloat32(value); v.IsValid() {
converted := float32(v.Float())
return reflect.ValueOf(&converted)
}

Check warning on line 158 in converter.go

View check run for this annotation

Codecov / codecov/patch

converter.go#L156-L158

Added lines #L156 - L158 were not covered by tests
case float64Type:
v := float64(convertFloat64(value).Float())
return reflect.ValueOf(&v)
if v := convertFloat64(value); v.IsValid() {
converted := float64(v.Float())
return reflect.ValueOf(&converted)
}
case intType:
v := int(convertInt(value).Int())
return reflect.ValueOf(&v)
if v := convertInt(value); v.IsValid() {
converted := int(v.Int())
return reflect.ValueOf(&converted)
}
case int8Type:
v := int8(convertInt8(value).Int())
return reflect.ValueOf(&v)
if v := convertInt8(value); v.IsValid() {
converted := int8(v.Int())
return reflect.ValueOf(&converted)
}
case int16Type:
v := int16(convertInt16(value).Int())
return reflect.ValueOf(&v)
if v := convertInt16(value); v.IsValid() {
converted := int16(v.Int())
return reflect.ValueOf(&converted)
}
case int32Type:
v := int32(convertInt32(value).Int())
return reflect.ValueOf(&v)
if v := convertInt32(value); v.IsValid() {
converted := int32(v.Int())
return reflect.ValueOf(&converted)
}
case int64Type:
v := int64(convertInt64(value).Int())
return reflect.ValueOf(&v)
if v := convertInt64(value); v.IsValid() {
converted := int64(v.Int())
return reflect.ValueOf(&converted)
}

Check warning on line 188 in converter.go

View check run for this annotation

Codecov / codecov/patch

converter.go#L169-L188

Added lines #L169 - L188 were not covered by tests
case stringType:
v := convertString(value).String()
return reflect.ValueOf(&v)
if v := convertString(value); v.IsValid() {
converted := v.String()
return reflect.ValueOf(&converted)
}
case uintType:
v := uint(convertUint(value).Uint())
return reflect.ValueOf(&v)
if v := convertUint(value); v.IsValid() {
converted := uint(v.Uint())
return reflect.ValueOf(&converted)
}
case uint8Type:
v := uint8(convertUint8(value).Uint())
return reflect.ValueOf(&v)
if v := convertUint8(value); v.IsValid() {
converted := uint8(v.Uint())
return reflect.ValueOf(&converted)
}
case uint16Type:
v := uint16(convertUint16(value).Uint())
return reflect.ValueOf(&v)
if v := convertUint16(value); v.IsValid() {
converted := uint16(v.Uint())
return reflect.ValueOf(&converted)
}
case uint32Type:
v := uint32(convertUint32(value).Uint())
return reflect.ValueOf(&v)
if v := convertUint32(value); v.IsValid() {
converted := uint32(v.Uint())
return reflect.ValueOf(&converted)
}
case uint64Type:
v := uint64(convertUint64(value).Uint())
return reflect.ValueOf(&v)
if v := convertUint64(value); v.IsValid() {
converted := uint64(v.Uint())
return reflect.ValueOf(&converted)
}

Check warning on line 218 in converter.go

View check run for this annotation

Codecov / codecov/patch

converter.go#L199-L218

Added lines #L199 - L218 were not covered by tests
}

return invalidValue
Expand Down
34 changes: 28 additions & 6 deletions decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,20 +116,42 @@ func (d *Decoder) setDefaults(t reflect.Type, v reflect.Value) MultiError {
if f.defaultValue != "" && f.isRequired {
errs.merge(MultiError{"default-" + f.name: errors.New("required fields cannot have a default value")})
} else if f.defaultValue != "" && vCurrent.IsZero() && !f.isRequired {
if f.typ.Kind() == reflect.Struct || f.typ.Kind() == reflect.Slice {
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")})
if f.typ.Kind() == reflect.Struct {
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")})
} else if f.typ.Kind() == reflect.Slice {
vals := strings.Split(f.defaultValue, "|")

//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
if convertedVal := builtinConverters[f.typ.Elem().Kind()](val); convertedVal.IsValid() {
defaultSlice = reflect.Append(defaultSlice, convertedVal)
}
}
vCurrent.Set(defaultSlice)
} else if f.typ.Kind() == reflect.Ptr {
t1 := f.typ.Elem()

if t1.Kind() == reflect.Struct || t1.Kind() == reflect.Slice {
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")})
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")})
}

Check warning on line 143 in decoder.go

View check run for this annotation

Codecov / codecov/patch

decoder.go#L142-L143

Added lines #L142 - L143 were not covered by tests

vCurrent.Set(convertPointer(t1.Kind(), f.defaultValue))
//this check is to handle if the wrong value is provided
if convertedVal := convertPointer(t1.Kind(), f.defaultValue); convertedVal.IsValid() {
vCurrent.Set(convertedVal)
}
} else {
vCurrent.Set(builtinConverters[f.typ.Kind()](f.defaultValue))
//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
60 changes: 49 additions & 11 deletions decoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2059,17 +2059,19 @@ func TestUnmashalPointerToEmbedded(t *testing.T) {
func TestDefaultValuesAreSet(t *testing.T) {

type N struct {
S1 string `schema:"s1,default:test1"`
I2 int `schema:"i2,default:22"`
S1 string `schema:"s1,default:test1"`
I2 int `schema:"i2,default:22"`
R2 []float64 `schema:"r2,default:2|3.5|11.01"`
}

type D struct {
N
S string `schema:"s,default:test1"`
I int `schema:"i,default:21"`
B bool `schema:"b,default:false"`
F float64 `schema:"f,default:3.14"`
U uint `schema:"u,default:1"`
S string `schema:"s,default:test1"`
I int `schema:"i,default:21"`
B bool `schema:"b,default:false"`
F float64 `schema:"f,default:3.14"`
U uint `schema:"u,default:1"`
X []string `schema:"x,default:x1|x2"`
}

data := map[string][]string{}
Expand All @@ -2086,12 +2088,14 @@ func TestDefaultValuesAreSet(t *testing.T) {
N: N{
S1: "test1",
I2: 22,
R2: []float64{2, 3.5, 11.01},
},
S: "test1",
I: 21,
B: false,
F: 3.14,
U: 1,
X: []string{"x1", "x2"},
}

if !reflect.DeepEqual(expected, d) {
Expand All @@ -2105,6 +2109,7 @@ func TestDefaultValuesAreSet(t *testing.T) {
B *bool `schema:"b,default:false"`
F *float64 `schema:"f,default:3.14"`
U *uint `schema:"u,default:1"`
X []string `schema:"x,default:x1|x2"`
}

p := P{N: &N{}}
Expand Down Expand Up @@ -2184,11 +2189,44 @@ func TestRequiredFieldsCannotHaveDefaults(t *testing.T) {

}

func TestDefaultsAreNotSupportedForStructsAndSlices(t *testing.T) {
func TestInvalidDefaultsValuesHaveNoEffect(t *testing.T) {

type D struct {
A []int `schema:"a,default:wrong1|wrong2"`
B bool `schema:"b,default:invalid"`
C *float32 `schema:"c,default:notAFloat"`
D uint8 `schema:"d,default:8000000"`
}

d := D{}

expected := D{A: []int{}}

data := map[string][]string{}

decoder := NewDecoder()

err := decoder.Decode(&d, data)

if err != nil {
t.Errorf("decoding should succeed but got error: %q", err)
}

if !reflect.DeepEqual(expected, d) {
t.Errorf("expected %v but got %v", expected, d)
}
}

func TestDefaultsAreNotSupportedForStructsAndStructSlices(t *testing.T) {

type C struct {
C string `schema:"c"`
}

type D struct {
S S1 `schema:"s,default:{f1:0}"`
A []string `schema:"s,default:test1,test2"`
S S1 `schema:"s,default:{f1:0}"`
A []C `schema:"a,default:{c:test1}|{c:test2}"`
B []*int `schema:"b,default:12"`
}

d := D{}
Expand All @@ -2199,7 +2237,7 @@ func TestDefaultsAreNotSupportedForStructsAndSlices(t *testing.T) {

err := decoder.Decode(&d, data)

expected := "default option is supported only on: bool, float variants, string, unit variants types or their corresponding pointers"
expected := "default option is supported only on: bool, float variants, string, unit variants types or their corresponding pointers or slices"

if err == nil || !strings.Contains(err.Error(), expected) {
t.Errorf("decoding should fail with error msg %s got %q", expected, err)
Expand Down

0 comments on commit 62c85ed

Please sign in to comment.