Repository: goccy/go-yaml
Pull request #55 (2019-12-10 05:46:08)

Returns decoding error for type mismatch or overflow number
resolve https://github.com/goccy/go-yaml/issues/53

Returns error like the following

```go
type st struct {
  A int `json:"a"`
}
d := yaml.NewDecoder(strings.NewReader("a: string"))
var a st
err := d.Decode(&a)
fmt.Println(a.A) // 0
fmt.Println(err) // cannot unmarshal string into Go struct field st.A of type int
```

Also, supports about overflow number too.

```go
var v map[string]int8
err := yaml.Unmarshal([]byte("a: 128"), &v)
fmt.Println(v)   // map[a:0]
fmt.Println(err) // cannot unmarshal 128 into Go value of type int8 ( overflow )
```


Patch:
diff --git a/decode.go b/decode.go
index 9ea2339..73f96d9 100644
--- a/decode.go
+++ b/decode.go
@@ -213,28 +213,59 @@ func (d *Decoder) fileToNode(f *ast.File) ast.Node {
 	return nil
 }
 
-func (d *Decoder) convertValue(v reflect.Value, typ reflect.Type) reflect.Value {
+func (d *Decoder) convertValue(v reflect.Value, typ reflect.Type) (reflect.Value, error) {
 	if typ.Kind() != reflect.String {
-		return v.Convert(typ)
+		if !v.Type().ConvertibleTo(typ) {
+			return reflect.Zero(typ), errTypeMismatch(typ, v.Type())
+		}
+		return v.Convert(typ), nil
 	}
 	// cast value to string
 	switch v.Type().Kind() {
 	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
-		return reflect.ValueOf(fmt.Sprint(v.Int()))
+		return reflect.ValueOf(fmt.Sprint(v.Int())), nil
 	case reflect.Float32, reflect.Float64:
-		return reflect.ValueOf(fmt.Sprint(v.Float()))
+		return reflect.ValueOf(fmt.Sprint(v.Float())), nil
 	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
-		return reflect.ValueOf(fmt.Sprint(v.Uint()))
+		return reflect.ValueOf(fmt.Sprint(v.Uint())), nil
 	case reflect.Bool:
-		return reflect.ValueOf(fmt.Sprint(v.Bool()))
+		return reflect.ValueOf(fmt.Sprint(v.Bool())), nil
+	}
+	if !v.Type().ConvertibleTo(typ) {
+		return reflect.Zero(typ), errTypeMismatch(typ, v.Type())
 	}
-	return v.Convert(typ)
+	return v.Convert(typ), nil
 }
 
-var (
-	errOverflowNumber = xerrors.New("overflow number")
-	errTypeMismatch   = xerrors.New("type mismatch")
-)
+type overflowError struct {
+	dstType reflect.Type
+	srcNum  string
+}
+
+func (e *overflowError) Error() string {
+	return fmt.Sprintf("cannot unmarshal %s into Go value of type %s ( overflow )", e.srcNum, e.dstType)
+}
+
+func errOverflow(dstType reflect.Type, num string) *overflowError {
+	return &overflowError{dstType: dstType, srcNum: num}
+}
+
+type typeError struct {
+	dstType         reflect.Type
+	srcType         reflect.Type
+	structFieldName *string
+}
+
+func (e *typeError) Error() string {
+	if e.structFieldName != nil {
+		return fmt.Sprintf("cannot unmarshal %s into Go struct field %s of type %s", e.srcType, *e.structFieldName, e.dstType)
+	}
+	return fmt.Sprintf("cannot unmarshal %s into Go value of type %s", e.srcType, e.dstType)
+}
+
+func errTypeMismatch(dstType, srcType reflect.Type) *typeError {
+	return &typeError{dstType: dstType, srcType: srcType}
+}
 
 func (d *Decoder) decodeValue(dst reflect.Value, src ast.Node) error {
 	valueType := dst.Type()
@@ -309,9 +340,9 @@ func (d *Decoder) decodeValue(dst reflect.Value, src ast.Node) error {
 				return nil
 			}
 		default:
-			return errTypeMismatch
+			return errTypeMismatch(valueType, reflect.TypeOf(v))
 		}
-		return errOverflowNumber
+		return errOverflow(valueType, fmt.Sprint(v))
 	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
 		v := d.nodeToValue(src)
 		switch vv := v.(type) {
@@ -331,13 +362,17 @@ func (d *Decoder) decodeValue(dst reflect.Value, src ast.Node) error {
 				return nil
 			}
 		default:
-			return errTypeMismatch
+			return errTypeMismatch(valueType, reflect.TypeOf(v))
 		}
-		return errOverflowNumber
+		return errOverflow(valueType, fmt.Sprint(v))
 	}
 	v := reflect.ValueOf(d.nodeToValue(src))
 	if v.IsValid() {
-		dst.Set(d.convertValue(v, dst.Type()))
+		convertedValue, err := d.convertValue(v, dst.Type())
+		if err != nil {
+			return errors.Wrapf(err, "failed to convert value")
+		}
+		dst.Set(convertedValue)
 	}
 	return nil
 }
@@ -449,7 +484,7 @@ func (d *Decoder) castToTime(src ast.Node) (time.Time, error) {
 	}
 	s, ok := v.(string)
 	if !ok {
-		return time.Time{}, errTypeMismatch
+		return time.Time{}, errTypeMismatch(reflect.TypeOf(time.Time{}), reflect.TypeOf(v))
 	}
 	for _, format := range allowedTimestampFormats {
 		t, err := time.Parse(format, s)
@@ -485,6 +520,9 @@ func (d *Decoder) decodeStruct(dst reflect.Value, src ast.Node) error {
 	if err != nil {
 		return errors.Wrapf(err, "failed to get keyToNodeMap")
 	}
+
+	var foundErr error
+
 	for i := 0; i < structType.NumField(); i++ {
 		field := structType.Field(i)
 		if isIgnoredStructField(field) {
@@ -503,11 +541,23 @@ func (d *Decoder) decodeStruct(dst reflect.Value, src ast.Node) error {
 			}
 			newFieldValue := d.createDecodableValue(fieldValue.Type())
 			if err := d.decodeValue(newFieldValue, src); err != nil {
-				if xerrors.Is(err, errTypeMismatch) || xerrors.Is(err, errOverflowNumber) {
-					// skip decoding if an error occurs
+				if foundErr != nil {
 					continue
 				}
-				return errors.Wrapf(err, "failed to decode value")
+				var te *typeError
+				if xerrors.As(err, &te) {
+					if te.structFieldName != nil {
+						fieldName := fmt.Sprintf("%s.%s", structType.Name(), *te.structFieldName)
+						te.structFieldName = &fieldName
+					} else {
+						fieldName := fmt.Sprintf("%s.%s", structType.Name(), field.Name)
+						te.structFieldName = &fieldName
+					}
+					foundErr = te
+				} else {
+					foundErr = err
+				}
+				continue
 			}
 			d.setDefaultValueIfConflicted(newFieldValue, structFieldMap)
 			fieldValue.Set(d.castToAssignableValue(newFieldValue, fieldValue.Type()))
@@ -525,11 +575,18 @@ func (d *Decoder) decodeStruct(dst reflect.Value, src ast.Node) error {
 		}
 		newFieldValue := d.createDecodableValue(fieldValue.Type())
 		if err := d.decodeValue(newFieldValue, v); err != nil {
-			if xerrors.Is(err, errTypeMismatch) || xerrors.Is(err, errOverflowNumber) {
-				// skip decoding if an error occurs
+			if foundErr != nil {
 				continue
 			}
-			return errors.Wrapf(err, "failed to decode value")
+			var te *typeError
+			if xerrors.As(err, &te) {
+				fieldName := fmt.Sprintf("%s.%s", structType.Name(), field.Name)
+				te.structFieldName = &fieldName
+				foundErr = te
+			} else {
+				foundErr = err
+			}
+			continue
 		}
 		fieldValue.Set(d.castToAssignableValue(newFieldValue, fieldValue.Type()))
 	}
@@ -554,6 +611,9 @@ func (d *Decoder) decodeStruct(dst reflect.Value, src ast.Node) error {
 		}
 	}
 	dst.Set(structValue.Elem())
+	if foundErr != nil {
+		return errors.Wrapf(foundErr, "failed to decode value")
+	}
 	return nil
 }
 
@@ -570,6 +630,8 @@ func (d *Decoder) decodeArray(dst reflect.Value, src ast.Node) error {
 	arrayType := dst.Type()
 	elemType := arrayType.Elem()
 	idx := 0
+
+	var foundErr error
 	for iter.Next() {
 		v := iter.Value()
 		if elemType.Kind() == reflect.Ptr && v.Type() == ast.NullType {
@@ -578,10 +640,10 @@ func (d *Decoder) decodeArray(dst reflect.Value, src ast.Node) error {
 		} else {
 			dstValue := d.createDecodableValue(elemType)
 			if err := d.decodeValue(dstValue, v); err != nil {
-				if xerrors.Is(err, errTypeMismatch) || xerrors.Is(err, errOverflowNumber) {
-					// skip decoding if an error occurs
+				if foundErr == nil {
+					foundErr = err
 				}
-				return errors.Wrapf(err, "failed to decode value")
+				continue
 			} else {
 				arrayValue.Index(idx).Set(d.castToAssignableValue(dstValue, elemType))
 			}
@@ -589,6 +651,9 @@ func (d *Decoder) decodeArray(dst reflect.Value, src ast.Node) error {
 		idx++
 	}
 	dst.Set(arrayValue)
+	if foundErr != nil {
+		return errors.Wrapf(foundErr, "failed to decode value")
+	}
 	return nil
 }
 
@@ -604,6 +669,8 @@ func (d *Decoder) decodeSlice(dst reflect.Value, src ast.Node) error {
 	sliceType := dst.Type()
 	sliceValue := reflect.MakeSlice(sliceType, 0, iter.Len())
 	elemType := sliceType.Elem()
+
+	var foundErr error
 	for iter.Next() {
 		v := iter.Value()
 		if elemType.Kind() == reflect.Ptr && v.Type() == ast.NullType {
@@ -613,15 +680,17 @@ func (d *Decoder) decodeSlice(dst reflect.Value, src ast.Node) error {
 		}
 		dstValue := d.createDecodableValue(elemType)
 		if err := d.decodeValue(dstValue, v); err != nil {
-			if xerrors.Is(err, errTypeMismatch) || xerrors.Is(err, errOverflowNumber) {
-				// skip decoding if an error occurs
-				continue
+			if foundErr == nil {
+				foundErr = err
 			}
-			return errors.Wrapf(err, "failed to decode value")
+			continue
 		}
 		sliceValue = reflect.Append(sliceValue, d.castToAssignableValue(dstValue, elemType))
 	}
 	dst.Set(sliceValue)
+	if foundErr != nil {
+		return errors.Wrapf(foundErr, "failed to decode value")
+	}
 	return nil
 }
 
@@ -638,6 +707,8 @@ func (d *Decoder) decodeMap(dst reflect.Value, src ast.Node) error {
 	keyType := mapValue.Type().Key()
 	valueType := mapValue.Type().Elem()
 	mapIter := mapNode.MapRange()
+
+	var foundErr error
 	for mapIter.Next() {
 		key := mapIter.Key()
 		value := mapIter.Value()
@@ -652,11 +723,9 @@ func (d *Decoder) decodeMap(dst reflect.Value, src ast.Node) error {
 		}
 		dstValue := d.createDecodableValue(valueType)
 		if err := d.decodeValue(dstValue, value); err != nil {
-			if xerrors.Is(err, errTypeMismatch) || xerrors.Is(err, errOverflowNumber) {
-				// skip decoding if an error occurs
-				continue
+			if foundErr == nil {
+				foundErr = err
 			}
-			return errors.Wrapf(err, "failed to decode value")
 		}
 		if !k.IsValid() {
 			// expect nil key
@@ -666,6 +735,9 @@ func (d *Decoder) decodeMap(dst reflect.Value, src ast.Node) error {
 		mapValue.SetMapIndex(k, d.castToAssignableValue(dstValue, valueType))
 	}
 	dst.Set(mapValue)
+	if foundErr != nil {
+		return errors.Wrapf(foundErr, "failed to decode value")
+	}
 	return nil
 }
 
diff --git a/internal/errors/error.go b/internal/errors/error.go
index 3bf3c15..39fae43 100644
--- a/internal/errors/error.go
+++ b/internal/errors/error.go
@@ -73,6 +73,18 @@ type myprinter struct {
 	inclSource bool
 }
 
+func (e *wrapError) As(target interface{}) bool {
+	err := e.nextErr
+	for {
+		if wrapErr, ok := err.(*wrapError); ok {
+			err = wrapErr.nextErr
+			continue
+		}
+		break
+	}
+	return xerrors.As(err, target)
+}
+
 func (e *wrapError) PrettyPrint(p xerrors.Printer, colored, inclSource bool) error {
 	return e.FormatError(&myprinter{Printer: p, colored: colored, inclSource: inclSource})
 }


Test patch:
diff --git a/decode_test.go b/decode_test.go
index 747fb2f..31b5abd 100644
--- a/decode_test.go
+++ b/decode_test.go
@@ -190,15 +190,9 @@ func TestDecoder(t *testing.T) {
 		{
 			"v: 42",
 			map[string]uint{"v": 42},
-		}, {
-			"v: -42",
-			map[string]uint{},
 		}, {
 			"v: 4294967296",
 			map[string]uint64{"v": 4294967296},
-		}, {
-			"v: -4294967296",
-			map[string]uint64{},
 		},
 
 		// int
@@ -238,10 +232,6 @@ func TestDecoder(t *testing.T) {
 			"v: 4294967295",
 			map[string]uint{"v": math.MaxUint32},
 		},
-		{
-			"v: -1",
-			map[string]uint{},
-		},
 
 		// uint64
 		{
@@ -260,10 +250,6 @@ func TestDecoder(t *testing.T) {
 			"v: 9223372036854775807",
 			map[string]uint64{"v": math.MaxInt64},
 		},
-		{
-			"v: -1",
-			map[string]uint64{},
-		},
 
 		// float32
 		{
@@ -328,15 +314,6 @@ func TestDecoder(t *testing.T) {
 			map[string]time.Time{"v": time.Date(2015, 2, 24, 18, 19, 39, 0, time.UTC)},
 		},
 
-		// Overflow cases.
-		{
-			"v: 4294967297",
-			map[string]int32{},
-		}, {
-			"v: 128",
-			map[string]int8{},
-		},
-
 		// Quoted values.
 		{
 			"'1': '\"2\"'",
@@ -478,10 +455,6 @@ func TestDecoder(t *testing.T) {
 			"v: [A,1,C]",
 			map[string][]string{"v": []string{"A", "1", "C"}},
 		},
-		{
-			"v: [A,1,C]",
-			map[string][]int{"v": []int{1}},
-		},
 		{
 			"v: [A,1,C]",
 			map[string]interface{}{"v": []interface{}{"A", 1, "C"}},
@@ -500,10 +473,6 @@ func TestDecoder(t *testing.T) {
 			"v:\n - A\n - 1\n - C",
 			map[string][]string{"v": []string{"A", "1", "C"}},
 		},
-		{
-			"v:\n - A\n - 1\n - C",
-			map[string][]int{"v": []int{1}},
-		},
 		{
 			"v:\n - A\n - 1\n - C",
 			map[string]interface{}{"v": []interface{}{"A", 1, "C"}},
@@ -875,6 +844,147 @@ func TestDecoder(t *testing.T) {
 	}
 }
 
+func TestDecoder_TypeConversionError(t *testing.T) {
+	t.Run("type conversion for struct", func(t *testing.T) {
+		type T struct {
+			A int
+			B uint
+			C float32
+			D bool
+		}
+		type U struct {
+			*T `yaml:",inline"`
+		}
+		t.Run("string to int", func(t *testing.T) {
+			var v T
+			err := yaml.Unmarshal([]byte(`a: str`), &v)
+			if err == nil {
+				t.Fatal("expected to error")
+			}
+			msg := "cannot unmarshal string into Go struct field T.A of type int"
+			if err.Error() != msg {
+				t.Fatalf("unexpected error message: %s. expect: %s", err.Error(), msg)
+			}
+		})
+		t.Run("string to bool", func(t *testing.T) {
+			var v T
+			err := yaml.Unmarshal([]byte(`d: str`), &v)
+			if err == nil {
+				t.Fatal("expected to error")
+			}
+			msg := "cannot unmarshal string into Go struct field T.D of type bool"
+			if err.Error() != msg {
+				t.Fatalf("unexpected error message: %s. expect: %s", err.Error(), msg)
+			}
+		})
+		t.Run("string to int at inline", func(t *testing.T) {
+			var v U
+			err := yaml.Unmarshal([]byte(`a: str`), &v)
+			if err == nil {
+				t.Fatal("expected to error")
+			}
+			msg := "cannot unmarshal string into Go struct field U.T.A of type int"
+			if err.Error() != msg {
+				t.Fatalf("unexpected error message: %s. expect: %s", err.Error(), msg)
+			}
+		})
+	})
+	t.Run("type conversion for array", func(t *testing.T) {
+		t.Run("string to int", func(t *testing.T) {
+			var v map[string][]int
+			err := yaml.Unmarshal([]byte(`v: [A,1,C]`), &v)
+			if err == nil {
+				t.Fatal("expected to error")
+			}
+			msg := "cannot unmarshal string into Go value of type int"
+			if err.Error() != msg {
+				t.Fatalf("unexpected error message: %s. expect: %s", err.Error(), msg)
+			}
+			if len(v) == 0 || len(v["v"]) == 0 {
+				t.Fatal("failed to decode value")
+			}
+			if v["v"][0] != 1 {
+				t.Fatal("failed to decode value")
+			}
+		})
+		t.Run("string to int", func(t *testing.T) {
+			var v map[string][]int
+			err := yaml.Unmarshal([]byte("v:\n - A\n - 1\n - C"), &v)
+			if err == nil {
+				t.Fatal("expected to error")
+			}
+			msg := "cannot unmarshal string into Go value of type int"
+			if err.Error() != msg {
+				t.Fatalf("unexpected error message: %s. expect: %s", err.Error(), msg)
+			}
+			if len(v) == 0 || len(v["v"]) == 0 {
+				t.Fatal("failed to decode value")
+			}
+			if v["v"][0] != 1 {
+				t.Fatal("failed to decode value")
+			}
+		})
+	})
+	t.Run("overflow error", func(t *testing.T) {
+		t.Run("negative number to uint", func(t *testing.T) {
+			var v map[string]uint
+			err := yaml.Unmarshal([]byte("v: -42"), &v)
+			if err == nil {
+				t.Fatal("expected to error")
+			}
+			msg := "cannot unmarshal -42 into Go value of type uint ( overflow )"
+			if err.Error() != msg {
+				t.Fatalf("unexpected error message: %s. expect: %s", err.Error(), msg)
+			}
+			if v["v"] != 0 {
+				t.Fatal("failed to decode value")
+			}
+		})
+		t.Run("negative number to uint64", func(t *testing.T) {
+			var v map[string]uint64
+			err := yaml.Unmarshal([]byte("v: -4294967296"), &v)
+			if err == nil {
+				t.Fatal("expected to error")
+			}
+			msg := "cannot unmarshal -4294967296 into Go value of type uint64 ( overflow )"
+			if err.Error() != msg {
+				t.Fatalf("unexpected error message: %s. expect: %s", err.Error(), msg)
+			}
+			if v["v"] != 0 {
+				t.Fatal("failed to decode value")
+			}
+		})
+		t.Run("larger number for int32", func(t *testing.T) {
+			var v map[string]int32
+			err := yaml.Unmarshal([]byte("v: 4294967297"), &v)
+			if err == nil {
+				t.Fatal("expected to error")
+			}
+			msg := "cannot unmarshal 4294967297 into Go value of type int32 ( overflow )"
+			if err.Error() != msg {
+				t.Fatalf("unexpected error message: %s. expect: %s", err.Error(), msg)
+			}
+			if v["v"] != 0 {
+				t.Fatal("failed to decode value")
+			}
+		})
+		t.Run("larger number for int8", func(t *testing.T) {
+			var v map[string]int8
+			err := yaml.Unmarshal([]byte("v: 128"), &v)
+			if err == nil {
+				t.Fatal("expected to error")
+			}
+			msg := "cannot unmarshal 128 into Go value of type int8 ( overflow )"
+			if err.Error() != msg {
+				t.Fatalf("unexpected error message: %s. expect: %s", err.Error(), msg)
+			}
+			if v["v"] != 0 {
+				t.Fatal("failed to decode value")
+			}
+		})
+	})
+}
+
 func TestDecoder_AnchorReferenceDirs(t *testing.T) {
 	buf := bytes.NewBufferString("a: *a\n")
 	dec := yaml.NewDecoder(buf, yaml.ReferenceDirs("testdata"))