From 4dca8b3e619a321de36b99b6b514d9c01ef76e5e Mon Sep 17 00:00:00 2001 From: Joe Tsai Date: Tue, 10 Feb 2026 11:34:35 -0800 Subject: encoding/json/v2: dual support errors.ErrUnsupported and SkipFunc This change supports both errors.ErrUnsupported and SkipFunc. In a future change, we will remove SkipFunc entirely in favor of using errors.ErrUnsupported, which exists for a similar pupose. Updates #74324 Change-Id: I5f1ada8e3914513d7d23c504f5965ca8dd95ad18 Reviewed-on: https://go-review.googlesource.com/c/go/+/745040 LUCI-TryBot-Result: Go LUCI Reviewed-by: Damien Neil Reviewed-by: Junyang Shao Reviewed-by: Johan Brandhorst-Satzkorn --- src/encoding/json/v2/arshal.go | 4 +- src/encoding/json/v2/arshal_funcs.go | 49 +++++++-------- src/encoding/json/v2/arshal_methods.go | 14 ++--- src/encoding/json/v2/arshal_test.go | 107 ++++++++++++++++----------------- src/encoding/json/v2/errors.go | 7 +++ src/encoding/json/v2/example_test.go | 8 +-- 6 files changed, 96 insertions(+), 93 deletions(-) (limited to 'src/encoding/json') diff --git a/src/encoding/json/v2/arshal.go b/src/encoding/json/v2/arshal.go index 5537a467d8..180a935c52 100644 --- a/src/encoding/json/v2/arshal.go +++ b/src/encoding/json/v2/arshal.go @@ -50,7 +50,7 @@ var export = jsontext.Internal.Export(&internal.AllowInternalUse) // // - If any type-specific functions in a [WithMarshalers] option match // the value type, then those functions are called to encode the value. -// If all applicable functions return [SkipFunc], +// If all applicable functions return [errors.ErrUnsupported], // then the value is encoded according to subsequent rules. // // - If the value type implements [MarshalerTo], @@ -265,7 +265,7 @@ func marshalEncode(out *jsontext.Encoder, in any, mo *jsonopts.Struct) (err erro // // - If any type-specific functions in a [WithUnmarshalers] option match // the value type, then those functions are called to decode the JSON -// value. If all applicable functions return [SkipFunc], +// value. If all applicable functions return [errors.ErrUnsupported], // then the input is decoded according to subsequent rules. // // - If the value type implements [UnmarshalerFrom], diff --git a/src/encoding/json/v2/arshal_funcs.go b/src/encoding/json/v2/arshal_funcs.go index 28916af948..1c325b16cb 100644 --- a/src/encoding/json/v2/arshal_funcs.go +++ b/src/encoding/json/v2/arshal_funcs.go @@ -26,9 +26,11 @@ import ( // For example, it is permissible to call [jsontext.Decoder.PeekKind], // but not permissible to call [jsontext.Decoder.ReadToken] or // [jsontext.Encoder.WriteToken] since such methods mutate the state. -var SkipFunc = errors.New("json: skip function") +// +// Deprecated: Use [errors.ErrUnsupported] instead. +var SkipFunc = errors.ErrUnsupported -var errSkipMutation = errors.New("must not read or write any tokens when skipping") +var errUnsupportedMutation = errors.New("unsupported calls must not read or write any tokens") var errNonSingularValue = errors.New("must read or write exactly one value") // Marshalers is a list of functions that may override the marshal behavior @@ -41,7 +43,8 @@ type Marshalers = typedMarshalers // JoinMarshalers constructs a flattened list of marshal functions. // If multiple functions in the list are applicable for a value of a given type, // then those earlier in the list take precedence over those that come later. -// If a function returns [SkipFunc], then the next applicable function is called, +// If a function returns [errors.ErrUnsupported], +// then the next applicable function is called, // otherwise the default marshaling behavior is used. // // For example: @@ -63,7 +66,8 @@ type Unmarshalers = typedUnmarshalers // JoinUnmarshalers constructs a flattened list of unmarshal functions. // If multiple functions in the list are applicable for a value of a given type, // then those earlier in the list take precedence over those that come later. -// If a function returns [SkipFunc], then the next applicable function is called, +// If a function returns [errors.ErrUnsupported], +// then the next applicable function is called, // otherwise the default unmarshaling behavior is used. // // For example: @@ -151,7 +155,7 @@ func (a *typedArshalers[Coder]) lookup(fnc func(*Coder, addressableValue, *jsono fncDefault := fnc fnc = func(c *Coder, v addressableValue, o *jsonopts.Struct) error { for _, fnc := range fncs { - if err := fnc(c, v, o); err != SkipFunc { + if err := fnc(c, v, o); !errors.Is(err, errors.ErrUnsupported) { return err // may be nil or non-nil } } @@ -171,7 +175,7 @@ func (a *typedArshalers[Coder]) lookup(fnc func(*Coder, addressableValue, *jsono // // The function must marshal exactly one JSON value. // The value of T must not be retained outside the function call. -// It may not return [SkipFunc]. +// It may not return [errors.ErrUnsupported]. func MarshalFunc[T any](fn func(T) ([]byte, error)) *Marshalers { t := reflect.TypeFor[T]() assertCastableTo(t, true) @@ -181,7 +185,7 @@ func MarshalFunc[T any](fn func(T) ([]byte, error)) *Marshalers { v, _ := reflect.TypeAssert[T](va.castTo(t)) val, err := fn(v) if err != nil { - err = wrapSkipFunc(err, "marshal function of type func(T) ([]byte, error)") + err = wrapErrUnsupported(err, "marshal function of type func(T) ([]byte, error)") if mo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { return internal.NewMarshalerError(va.Addr().Interface(), err, "MarshalFunc") // unlike unmarshal, always wrapped } @@ -210,9 +214,9 @@ func MarshalFunc[T any](fn func(T) ([]byte, error)) *Marshalers { // if T is an interface or pointer type. // // The function must marshal exactly one JSON value by calling write methods -// on the provided encoder. It may return [SkipFunc] such that marshaling can +// on the provided encoder. It may return [errors.ErrUnsupported] such that marshaling can // move on to the next marshal function. However, no mutable method calls may -// be called on the encoder if [SkipFunc] is returned. +// be called on the encoder if [errors.ErrUnsupported] is returned. // The pointer to [jsontext.Encoder] and the value of T // must not be retained outside the function call. func MarshalToFunc[T any](fn func(*jsontext.Encoder, T) error) *Marshalers { @@ -232,11 +236,11 @@ func MarshalToFunc[T any](fn func(*jsontext.Encoder, T) error) *Marshalers { err = errNonSingularValue } if err != nil { - if err == SkipFunc { + if errors.Is(err, errors.ErrUnsupported) { if prevDepth == currDepth && prevLength == currLength { - return SkipFunc + return err // forward [errors.ErrUnsupported] } - err = errSkipMutation + err = errUnsupportedMutation } if mo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { return internal.NewMarshalerError(va.Addr().Interface(), err, "MarshalToFunc") // unlike unmarshal, always wrapped @@ -261,7 +265,7 @@ func MarshalToFunc[T any](fn func(*jsontext.Encoder, T) error) *Marshalers { // The function must unmarshal exactly one JSON value. // The input []byte must not be mutated. // The input []byte and value T must not be retained outside the function call. -// It may not return [SkipFunc]. +// It may not return [errors.ErrUnsupported]. func UnmarshalFunc[T any](fn func([]byte, T) error) *Unmarshalers { t := reflect.TypeFor[T]() assertCastableTo(t, false) @@ -275,7 +279,7 @@ func UnmarshalFunc[T any](fn func([]byte, T) error) *Unmarshalers { v, _ := reflect.TypeAssert[T](va.castTo(t)) err = fn(val, v) if err != nil { - err = wrapSkipFunc(err, "unmarshal function of type func([]byte, T) error") + err = wrapErrUnsupported(err, "unmarshal function of type func([]byte, T) error") if uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { return err // unlike marshal, never wrapped } @@ -294,9 +298,9 @@ func UnmarshalFunc[T any](fn func([]byte, T) error) *Unmarshalers { // The function is always provided with a non-nil pointer value. // // The function must unmarshal exactly one JSON value by calling read methods -// on the provided decoder. It may return [SkipFunc] such that unmarshaling can +// on the provided decoder. It may return [errors.ErrUnsupported] such that unmarshaling can // move on to the next unmarshal function. However, no mutable method calls may -// be called on the decoder if [SkipFunc] is returned. +// be called on the decoder if [errors.ErrUnsupported] is returned. // The pointer to [jsontext.Decoder] and the value of T // must not be retained outside the function call. func UnmarshalFromFunc[T any](fn func(*jsontext.Decoder, T) error) *Unmarshalers { @@ -319,11 +323,11 @@ func UnmarshalFromFunc[T any](fn func(*jsontext.Decoder, T) error) *Unmarshalers err = errNonSingularValue } if err != nil { - if err == SkipFunc { + if errors.Is(err, errors.ErrUnsupported) { if prevDepth == currDepth && prevLength == currLength { - return SkipFunc + return err // forward [errors.ErrUnsupported] } - err = errSkipMutation + err = errUnsupportedMutation } if uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { if err2 := xd.SkipUntil(prevDepth, prevLength+1); err2 != nil { @@ -431,10 +435,3 @@ func castableToFromAny(to reflect.Type) bool { } return false } - -func wrapSkipFunc(err error, what string) error { - if err == SkipFunc { - return errors.New(what + " cannot be skipped") - } - return err -} diff --git a/src/encoding/json/v2/arshal_methods.go b/src/encoding/json/v2/arshal_methods.go index 1621eadc08..ed65ed7632 100644 --- a/src/encoding/json/v2/arshal_methods.go +++ b/src/encoding/json/v2/arshal_methods.go @@ -135,7 +135,7 @@ func makeMethodArshaler(fncs *arshaler, t reflect.Type) *arshaler { b2, err := marshaler.MarshalText() return append(b, b2...), err }); err != nil { - err = wrapSkipFunc(err, "marshal method") + err = wrapErrUnsupported(err, "MarshalText method") if mo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { return internal.NewMarshalerError(va.Addr().Interface(), err, "MarshalText") // unlike unmarshal, always wrapped } @@ -158,7 +158,7 @@ func makeMethodArshaler(fncs *arshaler, t reflect.Type) *arshaler { } appender, _ := reflect.TypeAssert[encoding.TextAppender](va.Addr()) if err := export.Encoder(enc).AppendRaw('"', false, appender.AppendText); err != nil { - err = wrapSkipFunc(err, "append method") + err = wrapErrUnsupported(err, "AppendText method") if mo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { return internal.NewMarshalerError(va.Addr().Interface(), err, "AppendText") // unlike unmarshal, always wrapped } @@ -182,7 +182,7 @@ func makeMethodArshaler(fncs *arshaler, t reflect.Type) *arshaler { marshaler, _ := reflect.TypeAssert[Marshaler](va.Addr()) val, err := marshaler.MarshalJSON() if err != nil { - err = wrapSkipFunc(err, "marshal method") + err = wrapErrUnsupported(err, "MarshalJSON method") if mo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { return internal.NewMarshalerError(va.Addr().Interface(), err, "MarshalJSON") // unlike unmarshal, always wrapped } @@ -221,7 +221,7 @@ func makeMethodArshaler(fncs *arshaler, t reflect.Type) *arshaler { err = errNonSingularValue } if err != nil { - err = wrapSkipFunc(err, "marshal method") + err = wrapErrUnsupported(err, "MarshalJSONTo method") if mo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { return internal.NewMarshalerError(va.Addr().Interface(), err, "MarshalJSONTo") // unlike unmarshal, always wrapped } @@ -255,7 +255,7 @@ func makeMethodArshaler(fncs *arshaler, t reflect.Type) *arshaler { s := jsonwire.UnquoteMayCopy(val, flags.IsVerbatim()) unmarshaler, _ := reflect.TypeAssert[encoding.TextUnmarshaler](va.Addr()) if err := unmarshaler.UnmarshalText(s); err != nil { - err = wrapSkipFunc(err, "unmarshal method") + err = wrapErrUnsupported(err, "UnmarshalText method") if uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { return err // unlike marshal, never wrapped } @@ -282,7 +282,7 @@ func makeMethodArshaler(fncs *arshaler, t reflect.Type) *arshaler { } unmarshaler, _ := reflect.TypeAssert[Unmarshaler](va.Addr()) if err := unmarshaler.UnmarshalJSON(val); err != nil { - err = wrapSkipFunc(err, "unmarshal method") + err = wrapErrUnsupported(err, "UnmarshalJSON method") if uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { return err // unlike marshal, never wrapped } @@ -315,7 +315,7 @@ func makeMethodArshaler(fncs *arshaler, t reflect.Type) *arshaler { err = errNonSingularValue } if err != nil { - err = wrapSkipFunc(err, "unmarshal method") + err = wrapErrUnsupported(err, "UnmarshalJSONFrom method") if uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { if err2 := xd.SkipUntil(prevDepth, prevLength+1); err2 != nil { return err2 diff --git a/src/encoding/json/v2/arshal_test.go b/src/encoding/json/v2/arshal_test.go index 1a7ed4b886..e8a9e79217 100644 --- a/src/encoding/json/v2/arshal_test.go +++ b/src/encoding/json/v2/arshal_test.go @@ -3393,11 +3393,11 @@ func TestMarshal(t *testing.T) { want: `nullnull`, wantErr: EM(errNonSingularValue).withPos(`nullnull`, "").withType(0, T[marshalJSONv2Func]()), }, { - name: jsontest.Name("Methods/Invalid/JSONv2/SkipFunc"), + name: jsontest.Name("Methods/Invalid/JSONv2/ErrUnsupported"), in: marshalJSONv2Func(func(enc *jsontext.Encoder) error { - return SkipFunc + return errors.ErrUnsupported }), - wantErr: EM(errors.New("marshal method cannot be skipped")).withType(0, T[marshalJSONv2Func]()), + wantErr: EM(errors.New("MarshalJSONTo method may not return errors.ErrUnsupported")).withType(0, T[marshalJSONv2Func]()), }, { name: jsontest.Name("Methods/Invalid/JSONv1/Error"), in: marshalJSONv1Func(func() ([]byte, error) { @@ -3411,11 +3411,11 @@ func TestMarshal(t *testing.T) { }), wantErr: EM(newInvalidCharacterError("i", "at start of value", 0, "")).withType(0, T[marshalJSONv1Func]()), }, { - name: jsontest.Name("Methods/Invalid/JSONv1/SkipFunc"), + name: jsontest.Name("Methods/Invalid/JSONv1/ErrUnsupported"), in: marshalJSONv1Func(func() ([]byte, error) { - return nil, SkipFunc + return nil, errors.ErrUnsupported }), - wantErr: EM(errors.New("marshal method cannot be skipped")).withType(0, T[marshalJSONv1Func]()), + wantErr: EM(errors.New("MarshalJSON method may not return errors.ErrUnsupported")).withType(0, T[marshalJSONv1Func]()), }, { name: jsontest.Name("Methods/AppendText"), in: appendTextFunc(func(b []byte) ([]byte, error) { return append(b, "hello"...), nil }), @@ -3457,11 +3457,11 @@ func TestMarshal(t *testing.T) { }), want: "\"\xde\xad\ufffd\ufffd\"", }, { - name: jsontest.Name("Methods/Invalid/Text/SkipFunc"), + name: jsontest.Name("Methods/Invalid/Text/ErrUnsupported"), in: marshalTextFunc(func() ([]byte, error) { - return nil, SkipFunc + return nil, errors.ErrUnsupported }), - wantErr: EM(wrapSkipFunc(SkipFunc, "marshal method")).withType(0, T[marshalTextFunc]()), + wantErr: EM(wrapErrUnsupported(errors.ErrUnsupported, "MarshalText method")).withType(0, T[marshalTextFunc]()), }, { name: jsontest.Name("Methods/Invalid/MapKey/JSONv2/Syntax"), in: map[any]string{ @@ -3586,11 +3586,11 @@ func TestMarshal(t *testing.T) { name: jsontest.Name("Functions/Bool/V1/SkipError"), opts: []Options{ WithMarshalers(MarshalFunc(func(bool) ([]byte, error) { - return nil, SkipFunc + return nil, errors.ErrUnsupported })), }, in: true, - wantErr: EM(wrapSkipFunc(SkipFunc, "marshal function of type func(T) ([]byte, error)")).withType(0, T[bool]()), + wantErr: EM(wrapErrUnsupported(errors.ErrUnsupported, "marshal function of type func(T) ([]byte, error)")).withType(0, T[bool]()), }, { name: jsontest.Name("Functions/Bool/V1/InvalidValue"), opts: []Options{ @@ -3634,7 +3634,7 @@ func TestMarshal(t *testing.T) { name: jsontest.Name("Functions/Bool/V2/Skipped"), opts: []Options{ WithMarshalers(MarshalToFunc(func(enc *jsontext.Encoder, v bool) error { - return SkipFunc + return errors.ErrUnsupported })), }, in: true, @@ -3644,21 +3644,21 @@ func TestMarshal(t *testing.T) { opts: []Options{ WithMarshalers(MarshalToFunc(func(enc *jsontext.Encoder, v bool) error { enc.WriteValue([]byte(`"hello"`)) - return SkipFunc + return errors.ErrUnsupported })), }, in: true, want: `"hello"`, - wantErr: EM(errSkipMutation).withPos(`"hello"`, "").withType(0, T[bool]()), + wantErr: EM(errUnsupportedMutation).withPos(`"hello"`, "").withType(0, T[bool]()), }, { - name: jsontest.Name("Functions/Bool/V2/WrappedSkipError"), + name: jsontest.Name("Functions/Bool/V2/WrappedUnsupportedError"), opts: []Options{ WithMarshalers(MarshalToFunc(func(enc *jsontext.Encoder, v bool) error { - return fmt.Errorf("wrap: %w", SkipFunc) + return fmt.Errorf("wrap: %w", errors.ErrUnsupported) })), }, - in: true, - wantErr: EM(fmt.Errorf("wrap: %w", SkipFunc)).withType(0, T[bool]()), + in: true, + want: `true`, }, { name: jsontest.Name("Functions/Map/Key/NoCaseString/V1"), opts: []Options{ @@ -4021,7 +4021,7 @@ func TestMarshal(t *testing.T) { return err } } - return SkipFunc + return errors.ErrUnsupported } makeValueChecker := func(name string, want []PV) func(e *jsontext.Encoder, v any) error { checkNext := func(e *jsontext.Encoder, v any) error { @@ -4038,7 +4038,7 @@ func TestMarshal(t *testing.T) { return fmt.Errorf("%s:\n\tgot %#v\n\twant %#v", name, pv, want[0]) default: want = want[1:] - return SkipFunc + return errors.ErrUnsupported } } lastChecks = append(lastChecks, func() error { @@ -4060,7 +4060,7 @@ func TestMarshal(t *testing.T) { return fmt.Errorf("%s: got %v, want %v", name, p, want[0]) default: want = want[1:] - return SkipFunc + return errors.ErrUnsupported } } lastChecks = append(lastChecks, func() error { @@ -4242,7 +4242,7 @@ func TestMarshal(t *testing.T) { opts: []Options{ WithMarshalers(JoinMarshalers( MarshalToFunc(func(enc *jsontext.Encoder, v bool) error { - return SkipFunc + return errors.ErrUnsupported }), MarshalFunc(func(bool) ([]byte, error) { return []byte(`"called"`), nil @@ -7800,12 +7800,12 @@ func TestUnmarshal(t *testing.T) { })), wantErr: EU(errNonSingularValue).withPos(`{}`, "").withType(0, T[unmarshalJSONv2Func]()), }, { - name: jsontest.Name("Methods/Invalid/JSONv2/SkipFunc"), + name: jsontest.Name("Methods/Invalid/JSONv2/ErrUnsupported"), inBuf: `{}`, inVal: addr(unmarshalJSONv2Func(func(*jsontext.Decoder) error { - return SkipFunc + return errors.ErrUnsupported })), - wantErr: EU(wrapSkipFunc(SkipFunc, "unmarshal method")).withType(0, T[unmarshalJSONv2Func]()), + wantErr: EU(wrapErrUnsupported(errors.ErrUnsupported, "UnmarshalJSONFrom method")).withType(0, T[unmarshalJSONv2Func]()), }, { name: jsontest.Name("Methods/Invalid/JSONv1/Error"), inBuf: `{}`, @@ -7814,12 +7814,12 @@ func TestUnmarshal(t *testing.T) { })), wantErr: EU(errSomeError).withType('{', T[unmarshalJSONv1Func]()), }, { - name: jsontest.Name("Methods/Invalid/JSONv1/SkipFunc"), + name: jsontest.Name("Methods/Invalid/JSONv1/ErrUnsupported"), inBuf: `{}`, inVal: addr(unmarshalJSONv1Func(func([]byte) error { - return SkipFunc + return errors.ErrUnsupported })), - wantErr: EU(wrapSkipFunc(SkipFunc, "unmarshal method")).withType('{', T[unmarshalJSONv1Func]()), + wantErr: EU(wrapErrUnsupported(errors.ErrUnsupported, "UnmarshalJSON method")).withType('{', T[unmarshalJSONv1Func]()), }, { name: jsontest.Name("Methods/Invalid/Text/Error"), inBuf: `"value"`, @@ -7835,12 +7835,12 @@ func TestUnmarshal(t *testing.T) { })), wantErr: EU(errNonStringValue).withType('{', T[unmarshalTextFunc]()), }, { - name: jsontest.Name("Methods/Invalid/Text/SkipFunc"), + name: jsontest.Name("Methods/Invalid/Text/ErrUnsupported"), inBuf: `"value"`, inVal: addr(unmarshalTextFunc(func([]byte) error { - return SkipFunc + return errors.ErrUnsupported })), - wantErr: EU(wrapSkipFunc(SkipFunc, "unmarshal method")).withType('"', T[unmarshalTextFunc]()), + wantErr: EU(wrapErrUnsupported(errors.ErrUnsupported, "UnmarshalText method")).withType('"', T[unmarshalTextFunc]()), }, { name: jsontest.Name("Functions/String/V1"), opts: []Options{ @@ -7960,13 +7960,13 @@ func TestUnmarshal(t *testing.T) { name: jsontest.Name("Functions/String/V1/SkipError"), opts: []Options{ WithUnmarshalers(UnmarshalFunc(func([]byte, *string) error { - return SkipFunc + return errors.ErrUnsupported })), }, inBuf: `""`, inVal: addr(""), want: addr(""), - wantErr: EU(wrapSkipFunc(SkipFunc, "unmarshal function of type func([]byte, T) error")).withType('"', reflect.PointerTo(stringType)), + wantErr: EU(wrapErrUnsupported(errors.ErrUnsupported, "unmarshal function of type func([]byte, T) error")).withType('"', reflect.PointerTo(stringType)), }, { name: jsontest.Name("Functions/String/V2/DirectError"), opts: []Options{ @@ -8010,7 +8010,7 @@ func TestUnmarshal(t *testing.T) { name: jsontest.Name("Functions/String/V2/Skipped"), opts: []Options{ WithUnmarshalers(UnmarshalFromFunc(func(dec *jsontext.Decoder, v *string) error { - return SkipFunc + return errors.ErrUnsupported })), }, inBuf: `""`, @@ -8023,24 +8023,23 @@ func TestUnmarshal(t *testing.T) { if _, err := dec.ReadValue(); err != nil { return err } - return SkipFunc + return errors.ErrUnsupported })), }, inBuf: `""`, inVal: addr(""), want: addr(""), - wantErr: EU(errSkipMutation).withType(0, reflect.PointerTo(stringType)), + wantErr: EU(errUnsupportedMutation).withType(0, reflect.PointerTo(stringType)), }, { - name: jsontest.Name("Functions/String/V2/WrappedSkipError"), + name: jsontest.Name("Functions/String/V2/WrappedUnsupported"), opts: []Options{ WithUnmarshalers(UnmarshalFromFunc(func(dec *jsontext.Decoder, v *string) error { - return fmt.Errorf("wrap: %w", SkipFunc) + return fmt.Errorf("wrap: %w", errors.ErrUnsupported) })), }, - inBuf: `""`, - inVal: addr(""), - want: addr(""), - wantErr: EU(fmt.Errorf("wrap: %w", SkipFunc)).withType(0, reflect.PointerTo(stringType)), + inBuf: `""`, + inVal: addr(""), + want: addr(""), }, { name: jsontest.Name("Functions/Map/Key/NoCaseString/V1"), opts: []Options{ @@ -8307,7 +8306,7 @@ func TestUnmarshal(t *testing.T) { opts: []Options{ WithUnmarshalers(UnmarshalFromFunc(func(dec *jsontext.Decoder, v *fmt.Stringer) error { *v = net.IP{} - return SkipFunc + return errors.ErrUnsupported })), }, inBuf: `{"X":"1.1.1.1"}`, @@ -8318,7 +8317,7 @@ func TestUnmarshal(t *testing.T) { opts: []Options{ WithUnmarshalers(UnmarshalFromFunc(func(dec *jsontext.Decoder, v *fmt.Stringer) error { *v = new(net.IP) - return SkipFunc + return errors.ErrUnsupported })), }, inBuf: `{"X":"1.1.1.1"}`, @@ -8329,7 +8328,7 @@ func TestUnmarshal(t *testing.T) { opts: []Options{ WithUnmarshalers(UnmarshalFromFunc(func(dec *jsontext.Decoder, v *fmt.Stringer) error { *v = (*net.IP)(nil) - return SkipFunc + return errors.ErrUnsupported })), }, inBuf: `{"X":"1.1.1.1"}`, @@ -8341,7 +8340,7 @@ func TestUnmarshal(t *testing.T) { WithUnmarshalers(JoinUnmarshalers( UnmarshalFromFunc(func(dec *jsontext.Decoder, v *fmt.Stringer) error { *v = (*net.IP)(nil) - return SkipFunc + return errors.ErrUnsupported }), UnmarshalFunc(func(b []byte, v *net.IP) error { b = bytes.ReplaceAll(b, []byte(`1`), []byte(`8`)) @@ -8389,7 +8388,7 @@ func TestUnmarshal(t *testing.T) { return err } } - return SkipFunc + return errors.ErrUnsupported } makeValueChecker := func(name string, want []PV) func(d *jsontext.Decoder, v any) error { checkNext := func(d *jsontext.Decoder, v any) error { @@ -8406,7 +8405,7 @@ func TestUnmarshal(t *testing.T) { return fmt.Errorf("%s:\n\tgot %#v\n\twant %#v", name, pv, want[0]) default: want = want[1:] - return SkipFunc + return errors.ErrUnsupported } } lastChecks = append(lastChecks, func() error { @@ -8428,7 +8427,7 @@ func TestUnmarshal(t *testing.T) { return fmt.Errorf("%s: got %v, want %v", name, p, want[0]) default: want = want[1:] - return SkipFunc + return errors.ErrUnsupported } } lastChecks = append(lastChecks, func() error { @@ -8622,7 +8621,7 @@ func TestUnmarshal(t *testing.T) { opts: []Options{ WithUnmarshalers(JoinUnmarshalers( UnmarshalFromFunc(func(dec *jsontext.Decoder, v *string) error { - return SkipFunc + return errors.ErrUnsupported }), UnmarshalFunc(func(b []byte, v *string) error { if string(b) != `"called"` { @@ -9350,7 +9349,7 @@ func TestUnmarshalDecodeOptions(t *testing.T) { } calledFuncs++ calledOptions = opts - return SkipFunc + return errors.ErrUnsupported })), // unmarshal-specific option; only relevant for UnmarshalDecode ) @@ -9389,7 +9388,7 @@ func TestUnmarshalDecodeOptions(t *testing.T) { t.Errorf("nested Options.AllowInvalidUTF8 = false, want true") } calledFuncs = math.MaxInt - return SkipFunc + return errors.ErrUnsupported })), // should override )); err != nil { t.Fatalf("UnmarshalDecode: %v", err) @@ -9489,7 +9488,7 @@ func TestMarshalEncodeOptions(t *testing.T) { } calledFuncs++ calledOptions = opts - return SkipFunc + return errors.ErrUnsupported })), // marshal-specific option; only relevant for MarshalEncode ) @@ -9528,7 +9527,7 @@ func TestMarshalEncodeOptions(t *testing.T) { t.Errorf("nested Options.AllowInvalidUTF8 = false, want true") } calledFuncs = math.MaxInt - return SkipFunc + return errors.ErrUnsupported })), // should override )); err != nil { t.Fatalf("MarshalEncode: %v", err) diff --git a/src/encoding/json/v2/errors.go b/src/encoding/json/v2/errors.go index 4895386fe2..da50bc30fc 100644 --- a/src/encoding/json/v2/errors.go +++ b/src/encoding/json/v2/errors.go @@ -299,6 +299,13 @@ func collapseSemanticErrors(err error) error { return err } +func wrapErrUnsupported(err error, what string) error { + if errors.Is(err, errors.ErrUnsupported) { + return errors.New(what + " may not return errors.ErrUnsupported") + } + return err +} + // errorModalVerb is a modal verb like "cannot" or "unable to". // // Once per process, Hyrum-proof the error message by deliberately diff --git a/src/encoding/json/v2/example_test.go b/src/encoding/json/v2/example_test.go index 684ca9c6a2..b95ad0f73c 100644 --- a/src/encoding/json/v2/example_test.go +++ b/src/encoding/json/v2/example_test.go @@ -550,8 +550,8 @@ func ExampleWithUnmarshalers_rawNumber() { if dec.PeekKind() == '0' { *val = jsontext.Value(nil) } - // Return SkipFunc to fallback on default unmarshal behavior. - return json.SkipFunc + // Return ErrUnsupported to fallback on default unmarshal behavior. + return errors.ErrUnsupported }), )) if err != nil { @@ -604,8 +604,8 @@ func ExampleWithUnmarshalers_recordOffsets() { n := len(unread) - len(bytes.TrimLeft(unread, " \n\r\t,:")) tunnel.ByteOffset = dec.InputOffset() + int64(n) - // Return SkipFunc to fallback on default unmarshal behavior. - return json.SkipFunc + // Return ErrUnsupported to fallback on default unmarshal behavior. + return errors.ErrUnsupported }), )) if err != nil { -- cgit v1.3