mirror of
https://github.com/golang/go.git
synced 2026-06-27 19:30:52 +00:00
encoding/json/jsontext: use custom wrapper type for Token accessor errors
In CL 772360, we made Token.Int, Token.Uint, and Token.Float accessors report an error when failing to convert to the resulting type. To be consistent with the rest of the package, it wrapped strconv.ErrRange and strconv.ErrSyntax within a SyntacticError. This is a bit odd, as it's not quite a SyntacticError and it also diminishes debugability since the error doesn't contain the original number value. Alternatively, we could use strconv.NumError, but that's also a poor fit since the error message carries a "strconv" prefix. Instead, let's use an unexported numError wrapper type that's analagous to strconv.NumError. Printing such an error now looks something like: jsontext.Token(1e1000).Int error: value out of range Updates #77666 Change-Id: I4cdbd478de7d1b105c048a70cb729634e841d500 Reviewed-on: https://go-review.googlesource.com/c/go/+/773961 Reviewed-by: Damien Neil <dneil@google.com> LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Michael Pratt <mpratt@google.com>
This commit is contained in:
parent
1bd98fab2c
commit
f2a43196d1
3 changed files with 37 additions and 25 deletions
|
|
@ -28,6 +28,19 @@ func (e *ioError) Unwrap() error {
|
|||
return e.err
|
||||
}
|
||||
|
||||
type numError struct {
|
||||
accessor string // either "Int", "Uint", or "Float"
|
||||
value string // e.g., "1e1000"
|
||||
err error // either [strconv.ErrSyntax] or [strconv.ErrRange]
|
||||
}
|
||||
|
||||
func (e *numError) Error() string {
|
||||
return "jsontext.Token(" + e.value + ")." + e.accessor + " error: " + e.err.Error()
|
||||
}
|
||||
func (e *numError) Unwrap() error {
|
||||
return e.err
|
||||
}
|
||||
|
||||
// SyntacticError is a description of a syntactic error that occurred when
|
||||
// encoding or decoding JSON according to the grammar.
|
||||
//
|
||||
|
|
|
|||
|
|
@ -381,7 +381,7 @@ func (t Token) float(bits int) (float64, error) {
|
|||
if Kind(buf[0]).normalize() == '0' {
|
||||
fv, err := strconv.ParseFloat(string(buf), bits)
|
||||
if err != nil {
|
||||
err = &SyntacticError{Err: errors.Unwrap(err)} // only ever ErrRange
|
||||
err = &numError{accessor: "Float", value: t.String(), err: errors.Unwrap(err)} // only ever ErrRange
|
||||
}
|
||||
return fv, err
|
||||
}
|
||||
|
|
@ -393,7 +393,7 @@ func (t Token) float(bits int) (float64, error) {
|
|||
case 'f':
|
||||
f64 := float64(math.Float64frombits(uint64(t.num)))
|
||||
if bits == 32 && !math.IsInf(f64, 0) && math.IsInf(float64(float32(f64)), 0) {
|
||||
return f64, &SyntacticError{Err: strconv.ErrRange}
|
||||
return f64, &numError{accessor: "Float", value: t.String(), err: strconv.ErrRange}
|
||||
}
|
||||
return f64, nil
|
||||
case 'i':
|
||||
|
|
@ -443,7 +443,7 @@ func (t Token) Int() (int64, error) {
|
|||
// Prospectively parse a negative integer.
|
||||
switch abs, ok := jsonwire.ParseUint(buf[len("-"):]); {
|
||||
case abs > -minInt64:
|
||||
return minInt64, &SyntacticError{Err: strconv.ErrRange}
|
||||
return minInt64, &numError{accessor: "Int", value: t.String(), err: strconv.ErrRange}
|
||||
case ok:
|
||||
return -1 * int64(abs), nil
|
||||
}
|
||||
|
|
@ -451,7 +451,7 @@ func (t Token) Int() (int64, error) {
|
|||
// Prospectively parse a non-negative integer.
|
||||
switch abs, ok := jsonwire.ParseUint(buf); {
|
||||
case abs > +maxInt64:
|
||||
return maxInt64, &SyntacticError{Err: strconv.ErrRange}
|
||||
return maxInt64, &numError{accessor: "Int", value: t.String(), err: strconv.ErrRange}
|
||||
case ok:
|
||||
return +1 * int64(abs), nil
|
||||
}
|
||||
|
|
@ -459,7 +459,7 @@ func (t Token) Int() (int64, error) {
|
|||
// This is not a signed integer, which implies ErrSyntax.
|
||||
if Kind(buf[0]).normalize() == '0' {
|
||||
f64, _ := strconv.ParseFloat(string(buf), 64)
|
||||
return f64toi64(f64), &SyntacticError{Err: strconv.ErrSyntax}
|
||||
return f64toi64(f64), &numError{accessor: "Int", value: t.String(), err: strconv.ErrSyntax}
|
||||
}
|
||||
} else if t.num != 0 {
|
||||
// Handle typed Go number value.
|
||||
|
|
@ -468,7 +468,7 @@ func (t Token) Int() (int64, error) {
|
|||
return int64(t.num), nil
|
||||
case 'u':
|
||||
if t.num > maxInt64 {
|
||||
return maxInt64, &SyntacticError{Err: strconv.ErrRange}
|
||||
return maxInt64, &numError{accessor: "Int", value: t.String(), err: strconv.ErrRange}
|
||||
}
|
||||
return int64(t.num), nil
|
||||
case 'f', 'F':
|
||||
|
|
@ -478,9 +478,9 @@ func (t Token) Int() (int64, error) {
|
|||
}
|
||||
switch i64 := f64toi64(f64); {
|
||||
case math.IsNaN(f64), math.Trunc(f64) != f64:
|
||||
return i64, &SyntacticError{Err: strconv.ErrSyntax}
|
||||
return i64, &numError{accessor: "Int", value: t.String(), err: strconv.ErrSyntax}
|
||||
case (i64 == minInt64 && f64 < minInt64) || (i64 == maxInt64 && f64 > maxInt64):
|
||||
return i64, &SyntacticError{Err: strconv.ErrRange}
|
||||
return i64, &numError{accessor: "Int", value: t.String(), err: strconv.ErrRange}
|
||||
default:
|
||||
return i64, nil
|
||||
}
|
||||
|
|
@ -533,12 +533,12 @@ func (t Token) Uint() (uint64, error) {
|
|||
case ok:
|
||||
return abs, nil
|
||||
case abs == maxUint64: // implies overflows
|
||||
return maxUint64, &SyntacticError{Err: strconv.ErrRange}
|
||||
return maxUint64, &numError{accessor: "Uint", value: t.String(), err: strconv.ErrRange}
|
||||
}
|
||||
// This is not an unsigned integer, which implies ErrSyntax.
|
||||
if Kind(buf[0]).normalize() == '0' {
|
||||
f64, _ := strconv.ParseFloat(string(buf), 64)
|
||||
return f64tou64(f64), &SyntacticError{Err: strconv.ErrSyntax}
|
||||
return f64tou64(f64), &numError{accessor: "Uint", value: t.String(), err: strconv.ErrSyntax}
|
||||
}
|
||||
} else if t.num != 0 {
|
||||
// Handle typed Go number value.
|
||||
|
|
@ -547,7 +547,7 @@ func (t Token) Uint() (uint64, error) {
|
|||
return t.num, nil
|
||||
case 'i':
|
||||
if int64(t.num) < minUint64 {
|
||||
return minUint64, &SyntacticError{Err: strconv.ErrSyntax}
|
||||
return minUint64, &numError{accessor: "Uint", value: t.String(), err: strconv.ErrSyntax}
|
||||
}
|
||||
return uint64(int64(t.num)), nil
|
||||
case 'f', 'F':
|
||||
|
|
@ -557,9 +557,9 @@ func (t Token) Uint() (uint64, error) {
|
|||
}
|
||||
switch u64 := f64tou64(f64); {
|
||||
case math.IsNaN(f64), math.Trunc(f64) != f64, math.Signbit(f64):
|
||||
return u64, &SyntacticError{Err: strconv.ErrSyntax}
|
||||
return u64, &numError{accessor: "Uint", value: t.String(), err: strconv.ErrSyntax}
|
||||
case (u64 == minUint64 && f64 < minUint64) || (u64 == maxUint64 && f64 > maxUint64):
|
||||
return u64, &SyntacticError{Err: strconv.ErrRange}
|
||||
return u64, &numError{accessor: "Uint", value: t.String(), err: strconv.ErrRange}
|
||||
default:
|
||||
return u64, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
package jsontext
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math"
|
||||
"reflect"
|
||||
"strconv"
|
||||
|
|
@ -47,18 +48,16 @@ func TestTokenAccessors(t *testing.T) {
|
|||
Kind Kind
|
||||
}
|
||||
negZero := math.Copysign(0, -1)
|
||||
errRange := &SyntacticError{Err: strconv.ErrRange}
|
||||
errSyntax := &SyntacticError{Err: strconv.ErrSyntax}
|
||||
f32 := func(f32 float32) valueError[float32] { return valueError[float32]{Value: f32} }
|
||||
f32er := func(f32 float32) valueError[float32] { return valueError[float32]{Value: f32, Error: errRange} }
|
||||
f32er := func(f32 float32) valueError[float32] { return valueError[float32]{Value: f32, Error: strconv.ErrRange} }
|
||||
f64 := func(f64 float64) valueError[float64] { return valueError[float64]{Value: f64} }
|
||||
f64er := func(f64 float64) valueError[float64] { return valueError[float64]{Value: f64, Error: errRange} }
|
||||
f64er := func(f64 float64) valueError[float64] { return valueError[float64]{Value: f64, Error: strconv.ErrRange} }
|
||||
i64 := func(i64 int64) valueError[int64] { return valueError[int64]{Value: i64} }
|
||||
i64er := func(i64 int64) valueError[int64] { return valueError[int64]{Value: i64, Error: errRange} }
|
||||
i64es := func(i64 int64) valueError[int64] { return valueError[int64]{Value: i64, Error: errSyntax} }
|
||||
i64er := func(i64 int64) valueError[int64] { return valueError[int64]{Value: i64, Error: strconv.ErrRange} }
|
||||
i64es := func(i64 int64) valueError[int64] { return valueError[int64]{Value: i64, Error: strconv.ErrSyntax} }
|
||||
u64 := func(u64 uint64) valueError[uint64] { return valueError[uint64]{Value: u64} }
|
||||
u64er := func(u64 uint64) valueError[uint64] { return valueError[uint64]{Value: u64, Error: errRange} }
|
||||
u64es := func(u64 uint64) valueError[uint64] { return valueError[uint64]{Value: u64, Error: errSyntax} }
|
||||
u64er := func(u64 uint64) valueError[uint64] { return valueError[uint64]{Value: u64, Error: strconv.ErrRange} }
|
||||
u64es := func(u64 uint64) valueError[uint64] { return valueError[uint64]{Value: u64, Error: strconv.ErrSyntax} }
|
||||
|
||||
tests := []struct {
|
||||
in Token
|
||||
|
|
@ -185,16 +184,16 @@ func TestTokenAccessors(t *testing.T) {
|
|||
if got.String != tt.want.String {
|
||||
t.Errorf("Token(%s).String() = %v, want %v", tt.in, got.String, tt.want.String)
|
||||
}
|
||||
if math.Float32bits(got.Float32.Value) != math.Float32bits(tt.want.Float32.Value) || !reflect.DeepEqual(got.Float32.Error, tt.want.Float32.Error) {
|
||||
if math.Float32bits(got.Float32.Value) != math.Float32bits(tt.want.Float32.Value) || !errors.Is(got.Float32.Error, tt.want.Float32.Error) {
|
||||
t.Errorf("Token(%s).Float32() = (%v, %v), want (%v, %v)", tt.in, got.Float32.Value, got.Float32.Error, tt.want.Float32.Value, tt.want.Float32.Error)
|
||||
}
|
||||
if math.Float64bits(got.Float.Value) != math.Float64bits(tt.want.Float.Value) || !reflect.DeepEqual(got.Float.Error, tt.want.Float.Error) {
|
||||
if math.Float64bits(got.Float.Value) != math.Float64bits(tt.want.Float.Value) || !errors.Is(got.Float.Error, tt.want.Float.Error) {
|
||||
t.Errorf("Token(%s).Float() = (%v, %v), want (%v, %v)", tt.in, got.Float.Value, got.Float.Error, tt.want.Float.Value, tt.want.Float.Error)
|
||||
}
|
||||
if got.Int.Value != tt.want.Int.Value || !reflect.DeepEqual(got.Int.Error, tt.want.Int.Error) {
|
||||
if got.Int.Value != tt.want.Int.Value || !errors.Is(got.Int.Error, tt.want.Int.Error) {
|
||||
t.Errorf("Token(%s).Int() = (%v, %v), want (%v, %v)", tt.in, got.Int.Value, got.Int.Error, tt.want.Int.Value, tt.want.Int.Error)
|
||||
}
|
||||
if got.Uint.Value != tt.want.Uint.Value || !reflect.DeepEqual(got.Uint.Error, tt.want.Uint.Error) {
|
||||
if got.Uint.Value != tt.want.Uint.Value || !errors.Is(got.Uint.Error, tt.want.Uint.Error) {
|
||||
t.Errorf("Token(%s).Uint() = (%v, %v), want (%v, %v)", tt.in, got.Uint.Value, got.Uint.Error, tt.want.Uint.Value, tt.want.Uint.Error)
|
||||
}
|
||||
if got.Kind != tt.want.Kind {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue