fix: parse integer fields as base 10
toInt and toUint passed base 0 to strconv, which infers the base from the literal the way the Go compiler does. A zero-padded CSV field was then read as octal: 08 and 09 failed with invalid syntax and 010 silently parsed as 8. Pass base 10 at both call sites. Go-style prefixed literals such as 0x1f are no longer accepted; they were undocumented and untested, and cannot be kept without keeping the octal reading of 010 as well. Closes #182
This commit is contained in:
1 parent
0ba11f2f3b
commit
26aafe16a8
3 files changed
+90
-2
No files matched your search
@@ -1359,3 +1359,33 @@ func Test_readTo_inline_nested_struct(t *testing.T) {
|
|||||||
t.Fatalf("expected \n sample: %v\n got: %v", expected, samples)
|
t.Fatalf("expected \n sample: %v\n got: %v", expected, samples)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalZeroPaddedNumbers(t *testing.T) {
|
||||||
|
type row struct {
|
||||||
|
Name string `csv:"name"`
|
||||||
|
Code int `csv:"code"`
|
||||||
|
Count uint `csv:"count"`
|
||||||
|
}
|
||||||
|
|
||||||
|
in := "name,code,count\n" +
|
||||||
|
"a,08,08\n" +
|
||||||
|
"b,09,09\n" +
|
||||||
|
"c,010,010\n" +
|
||||||
|
"d,007,007\n"
|
||||||
|
|
||||||
|
var got []row
|
||||||
|
if err := UnmarshalString(in, &got); err != nil {
|
||||||
|
t.Fatalf("UnmarshalString failed on zero-padded numbers: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
want := []row{
|
||||||
|
{Name: "a", Code: 8, Count: 8},
|
||||||
|
{Name: "b", Code: 9, Count: 9},
|
||||||
|
{Name: "c", Code: 10, Count: 10},
|
||||||
|
{Name: "d", Code: 7, Count: 7},
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(got, want) {
|
||||||
|
t.Errorf("UnmarshalString returned %+v, want %+v", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -130,7 +130,9 @@ func toInt(in interface{}) (int64, error) {
|
|||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
out := strings.SplitN(s, ".", 2)
|
out := strings.SplitN(s, ".", 2)
|
||||||
return strconv.ParseInt(out[0], 0, 64)
|
// base 10, not 0: a CSV field is data, not Go source, so "010" is ten
|
||||||
|
// and "09" is nine rather than an invalid octal literal
|
||||||
|
return strconv.ParseInt(out[0], 10, 64)
|
||||||
case reflect.Bool:
|
case reflect.Bool:
|
||||||
if inValue.Bool() {
|
if inValue.Bool() {
|
||||||
return 1, nil
|
return 1, nil
|
||||||
@@ -164,7 +166,8 @@ func toUint(in interface{}) (uint64, error) {
|
|||||||
}
|
}
|
||||||
return uint64(f), nil
|
return uint64(f), nil
|
||||||
}
|
}
|
||||||
return strconv.ParseUint(s, 0, 64)
|
// base 10, see toInt
|
||||||
|
return strconv.ParseUint(s, 10, 64)
|
||||||
case reflect.Bool:
|
case reflect.Bool:
|
||||||
if inValue.Bool() {
|
if inValue.Bool() {
|
||||||
return 1, nil
|
return 1, nil
|
||||||
|
|||||||
@@ -139,3 +139,58 @@ func TestToInt(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestToIntLeadingZeros(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
field string
|
||||||
|
result int64
|
||||||
|
}{
|
||||||
|
{"08", 8},
|
||||||
|
{"09", 9},
|
||||||
|
{"010", 10},
|
||||||
|
{"007", 7},
|
||||||
|
{"0009", 9},
|
||||||
|
{"-08", -8},
|
||||||
|
{"-010", -10},
|
||||||
|
{"0", 0},
|
||||||
|
{"00", 0},
|
||||||
|
{"010.9", 10},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, item := range testCases {
|
||||||
|
out, err := toInt(item.field)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("toInt(%q) returned an error: %s", item.field, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if out != item.result {
|
||||||
|
t.Errorf("toInt(%q) = %d, want %d", item.field, out, item.result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestToUintLeadingZeros(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
field string
|
||||||
|
result uint64
|
||||||
|
}{
|
||||||
|
{"08", 8},
|
||||||
|
{"09", 9},
|
||||||
|
{"010", 10},
|
||||||
|
{"007", 7},
|
||||||
|
{"0009", 9},
|
||||||
|
{"0", 0},
|
||||||
|
{"00", 0},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, item := range testCases {
|
||||||
|
out, err := toUint(item.field)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("toUint(%q) returned an error: %s", item.field, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if out != item.result {
|
||||||
|
t.Errorf("toUint(%q) = %d, want %d", item.field, out, item.result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user