Source file src/encoding/json/v2_stream_test.go

     1  // Copyright 2010 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  //go:build goexperiment.jsonv2
     6  
     7  package json
     8  
     9  import (
    10  	"bytes"
    11  	"errors"
    12  	"io"
    13  	"log"
    14  	"net"
    15  	"net/http"
    16  	"net/http/httptest"
    17  	"reflect"
    18  	"runtime/debug"
    19  	"strings"
    20  	"testing"
    21  
    22  	"encoding/json/internal/jsontest"
    23  )
    24  
    25  type CaseName = jsontest.CaseName
    26  type CasePos = jsontest.CasePos
    27  
    28  var Name = jsontest.Name
    29  
    30  // Test values for the stream test.
    31  // One of each JSON kind.
    32  var streamTest = []any{
    33  	0.1,
    34  	"hello",
    35  	nil,
    36  	true,
    37  	false,
    38  	[]any{"a", "b", "c"},
    39  	map[string]any{"K": "Kelvin", "ß": "long s"},
    40  	3.14, // another value to make sure something can follow map
    41  }
    42  
    43  var streamEncoded = `0.1
    44  "hello"
    45  null
    46  true
    47  false
    48  ["a","b","c"]
    49  {"ß":"long s","K":"Kelvin"}
    50  3.14
    51  `
    52  
    53  func TestEncoder(t *testing.T) {
    54  	for i := 0; i <= len(streamTest); i++ {
    55  		var buf strings.Builder
    56  		enc := NewEncoder(&buf)
    57  		// Check that enc.SetIndent("", "") turns off indentation.
    58  		enc.SetIndent(">", ".")
    59  		enc.SetIndent("", "")
    60  		for j, v := range streamTest[0:i] {
    61  			if err := enc.Encode(v); err != nil {
    62  				t.Fatalf("#%d.%d Encode error: %v", i, j, err)
    63  			}
    64  		}
    65  		if got, want := buf.String(), nlines(streamEncoded, i); got != want {
    66  			t.Errorf("encoding %d items: mismatch:", i)
    67  			diff(t, []byte(got), []byte(want))
    68  			break
    69  		}
    70  	}
    71  }
    72  
    73  func TestEncoderErrorAndReuseEncodeState(t *testing.T) {
    74  	// Disable the GC temporarily to prevent encodeState's in Pool being cleaned away during the test.
    75  	percent := debug.SetGCPercent(-1)
    76  	defer debug.SetGCPercent(percent)
    77  
    78  	// Trigger an error in Marshal with cyclic data.
    79  	type Dummy struct {
    80  		Name string
    81  		Next *Dummy
    82  	}
    83  	dummy := Dummy{Name: "Dummy"}
    84  	dummy.Next = &dummy
    85  
    86  	var buf bytes.Buffer
    87  	enc := NewEncoder(&buf)
    88  	if err := enc.Encode(dummy); err == nil {
    89  		t.Errorf("Encode(dummy) error: got nil, want non-nil")
    90  	}
    91  
    92  	type Data struct {
    93  		A string
    94  		I int
    95  	}
    96  	want := Data{A: "a", I: 1}
    97  	if err := enc.Encode(want); err != nil {
    98  		t.Errorf("Marshal error: %v", err)
    99  	}
   100  
   101  	var got Data
   102  	if err := Unmarshal(buf.Bytes(), &got); err != nil {
   103  		t.Errorf("Unmarshal error: %v", err)
   104  	}
   105  	if got != want {
   106  		t.Errorf("Marshal/Unmarshal roundtrip:\n\tgot:  %v\n\twant: %v", got, want)
   107  	}
   108  }
   109  
   110  var streamEncodedIndent = `0.1
   111  "hello"
   112  null
   113  true
   114  false
   115  [
   116  >."a",
   117  >."b",
   118  >."c"
   119  >]
   120  {
   121  >."ß": "long s",
   122  >."K": "Kelvin"
   123  >}
   124  3.14
   125  `
   126  
   127  func TestEncoderIndent(t *testing.T) {
   128  	var buf strings.Builder
   129  	enc := NewEncoder(&buf)
   130  	enc.SetIndent(">", ".")
   131  	for _, v := range streamTest {
   132  		enc.Encode(v)
   133  	}
   134  	if got, want := buf.String(), streamEncodedIndent; got != want {
   135  		t.Errorf("Encode mismatch:\ngot:\n%s\n\nwant:\n%s", got, want)
   136  		diff(t, []byte(got), []byte(want))
   137  	}
   138  }
   139  
   140  type strMarshaler string
   141  
   142  func (s strMarshaler) MarshalJSON() ([]byte, error) {
   143  	return []byte(s), nil
   144  }
   145  
   146  type strPtrMarshaler string
   147  
   148  func (s *strPtrMarshaler) MarshalJSON() ([]byte, error) {
   149  	return []byte(*s), nil
   150  }
   151  
   152  func TestEncoderSetEscapeHTML(t *testing.T) {
   153  	var c C
   154  	var ct CText
   155  	var tagStruct struct {
   156  		Valid   int `json:"<>&#! "`
   157  		Invalid int `json:"\\"`
   158  	}
   159  
   160  	// This case is particularly interesting, as we force the encoder to
   161  	// take the address of the Ptr field to use its MarshalJSON method. This
   162  	// is why the '&' is important.
   163  	marshalerStruct := &struct {
   164  		NonPtr strMarshaler
   165  		Ptr    strPtrMarshaler
   166  	}{`"<str>"`, `"<str>"`}
   167  
   168  	// https://golang.org/issue/34154
   169  	stringOption := struct {
   170  		Bar string `json:"bar,string"`
   171  	}{`<html>foobar</html>`}
   172  
   173  	tests := []struct {
   174  		CaseName
   175  		v          any
   176  		wantEscape string
   177  		want       string
   178  	}{
   179  		{Name("c"), c, `"\u003c\u0026\u003e"`, `"<&>"`},
   180  		{Name("ct"), ct, `"\"\u003c\u0026\u003e\""`, `"\"<&>\""`},
   181  		{Name(`"<&>"`), "<&>", `"\u003c\u0026\u003e"`, `"<&>"`},
   182  		{
   183  			Name("tagStruct"), tagStruct,
   184  			`{"\u003c\u003e\u0026#! ":0,"Invalid":0}`,
   185  			`{"<>&#! ":0,"Invalid":0}`,
   186  		},
   187  		{
   188  			Name(`"<str>"`), marshalerStruct,
   189  			`{"NonPtr":"\u003cstr\u003e","Ptr":"\u003cstr\u003e"}`,
   190  			`{"NonPtr":"<str>","Ptr":"<str>"}`,
   191  		},
   192  		{
   193  			Name("stringOption"), stringOption,
   194  			`{"bar":"\"\\u003chtml\\u003efoobar\\u003c/html\\u003e\""}`,
   195  			`{"bar":"\"<html>foobar</html>\""}`,
   196  		},
   197  	}
   198  	for _, tt := range tests {
   199  		t.Run(tt.Name, func(t *testing.T) {
   200  			var buf strings.Builder
   201  			enc := NewEncoder(&buf)
   202  			if err := enc.Encode(tt.v); err != nil {
   203  				t.Fatalf("%s: Encode(%s) error: %s", tt.Where, tt.Name, err)
   204  			}
   205  			if got := strings.TrimSpace(buf.String()); got != tt.wantEscape {
   206  				t.Errorf("%s: Encode(%s):\n\tgot:  %s\n\twant: %s", tt.Where, tt.Name, got, tt.wantEscape)
   207  			}
   208  			buf.Reset()
   209  			enc.SetEscapeHTML(false)
   210  			if err := enc.Encode(tt.v); err != nil {
   211  				t.Fatalf("%s: SetEscapeHTML(false) Encode(%s) error: %s", tt.Where, tt.Name, err)
   212  			}
   213  			if got := strings.TrimSpace(buf.String()); got != tt.want {
   214  				t.Errorf("%s: SetEscapeHTML(false) Encode(%s):\n\tgot:  %s\n\twant: %s",
   215  					tt.Where, tt.Name, got, tt.want)
   216  			}
   217  		})
   218  	}
   219  }
   220  
   221  func TestDecoder(t *testing.T) {
   222  	for i := 0; i <= len(streamTest); i++ {
   223  		// Use stream without newlines as input,
   224  		// just to stress the decoder even more.
   225  		// Our test input does not include back-to-back numbers.
   226  		// Otherwise stripping the newlines would
   227  		// merge two adjacent JSON values.
   228  		var buf bytes.Buffer
   229  		for _, c := range nlines(streamEncoded, i) {
   230  			if c != '\n' {
   231  				buf.WriteRune(c)
   232  			}
   233  		}
   234  		out := make([]any, i)
   235  		dec := NewDecoder(&buf)
   236  		for j := range out {
   237  			if err := dec.Decode(&out[j]); err != nil {
   238  				t.Fatalf("decode #%d/%d error: %v", j, i, err)
   239  			}
   240  		}
   241  		if !reflect.DeepEqual(out, streamTest[0:i]) {
   242  			t.Errorf("decoding %d items: mismatch:", i)
   243  			for j := range out {
   244  				if !reflect.DeepEqual(out[j], streamTest[j]) {
   245  					t.Errorf("#%d:\n\tgot:  %v\n\twant: %v", j, out[j], streamTest[j])
   246  				}
   247  			}
   248  			break
   249  		}
   250  	}
   251  }
   252  
   253  func TestDecoderBuffered(t *testing.T) {
   254  	r := strings.NewReader(`{"Name": "Gopher"} extra `)
   255  	var m struct {
   256  		Name string
   257  	}
   258  	d := NewDecoder(r)
   259  	err := d.Decode(&m)
   260  	if err != nil {
   261  		t.Fatal(err)
   262  	}
   263  	if m.Name != "Gopher" {
   264  		t.Errorf("Name = %s, want Gopher", m.Name)
   265  	}
   266  	rest, err := io.ReadAll(d.Buffered())
   267  	if err != nil {
   268  		t.Fatal(err)
   269  	}
   270  	if got, want := string(rest), " extra "; got != want {
   271  		t.Errorf("Remaining = %s, want %s", got, want)
   272  	}
   273  }
   274  
   275  func nlines(s string, n int) string {
   276  	if n <= 0 {
   277  		return ""
   278  	}
   279  	for i, c := range s {
   280  		if c == '\n' {
   281  			if n--; n == 0 {
   282  				return s[0 : i+1]
   283  			}
   284  		}
   285  	}
   286  	return s
   287  }
   288  
   289  func TestRawMessage(t *testing.T) {
   290  	var data struct {
   291  		X  float64
   292  		Id RawMessage
   293  		Y  float32
   294  	}
   295  	const raw = `["\u0056",null]`
   296  	const want = `{"X":0.1,"Id":["\u0056",null],"Y":0.2}`
   297  	err := Unmarshal([]byte(want), &data)
   298  	if err != nil {
   299  		t.Fatalf("Unmarshal error: %v", err)
   300  	}
   301  	if string([]byte(data.Id)) != raw {
   302  		t.Fatalf("Unmarshal:\n\tgot:  %s\n\twant: %s", []byte(data.Id), raw)
   303  	}
   304  	got, err := Marshal(&data)
   305  	if err != nil {
   306  		t.Fatalf("Marshal error: %v", err)
   307  	}
   308  	if string(got) != want {
   309  		t.Fatalf("Marshal:\n\tgot:  %s\n\twant: %s", got, want)
   310  	}
   311  }
   312  
   313  func TestNullRawMessage(t *testing.T) {
   314  	var data struct {
   315  		X     float64
   316  		Id    RawMessage
   317  		IdPtr *RawMessage
   318  		Y     float32
   319  	}
   320  	const want = `{"X":0.1,"Id":null,"IdPtr":null,"Y":0.2}`
   321  	err := Unmarshal([]byte(want), &data)
   322  	if err != nil {
   323  		t.Fatalf("Unmarshal error: %v", err)
   324  	}
   325  	if want, got := "null", string(data.Id); want != got {
   326  		t.Fatalf("Unmarshal:\n\tgot:  %s\n\twant: %s", got, want)
   327  	}
   328  	if data.IdPtr != nil {
   329  		t.Fatalf("pointer mismatch: got non-nil, want nil")
   330  	}
   331  	got, err := Marshal(&data)
   332  	if err != nil {
   333  		t.Fatalf("Marshal error: %v", err)
   334  	}
   335  	if string(got) != want {
   336  		t.Fatalf("Marshal:\n\tgot:  %s\n\twant: %s", got, want)
   337  	}
   338  }
   339  
   340  func TestBlocking(t *testing.T) {
   341  	tests := []struct {
   342  		CaseName
   343  		in string
   344  	}{
   345  		{Name(""), `{"x": 1}`},
   346  		{Name(""), `[1, 2, 3]`},
   347  	}
   348  	for _, tt := range tests {
   349  		t.Run(tt.Name, func(t *testing.T) {
   350  			r, w := net.Pipe()
   351  			go w.Write([]byte(tt.in))
   352  			var val any
   353  
   354  			// If Decode reads beyond what w.Write writes above,
   355  			// it will block, and the test will deadlock.
   356  			if err := NewDecoder(r).Decode(&val); err != nil {
   357  				t.Errorf("%s: NewDecoder(%s).Decode error: %v", tt.Where, tt.in, err)
   358  			}
   359  			r.Close()
   360  			w.Close()
   361  		})
   362  	}
   363  }
   364  
   365  type decodeThis struct {
   366  	v any
   367  }
   368  
   369  func TestDecodeInStream(t *testing.T) {
   370  	tests := []struct {
   371  		CaseName
   372  		json      string
   373  		expTokens []any
   374  	}{
   375  		// streaming token cases
   376  		{CaseName: Name(""), json: `10`, expTokens: []any{float64(10)}},
   377  		{CaseName: Name(""), json: ` [10] `, expTokens: []any{
   378  			Delim('['), float64(10), Delim(']')}},
   379  		{CaseName: Name(""), json: ` [false,10,"b"] `, expTokens: []any{
   380  			Delim('['), false, float64(10), "b", Delim(']')}},
   381  		{CaseName: Name(""), json: `{ "a": 1 }`, expTokens: []any{
   382  			Delim('{'), "a", float64(1), Delim('}')}},
   383  		{CaseName: Name(""), json: `{"a": 1, "b":"3"}`, expTokens: []any{
   384  			Delim('{'), "a", float64(1), "b", "3", Delim('}')}},
   385  		{CaseName: Name(""), json: ` [{"a": 1},{"a": 2}] `, expTokens: []any{
   386  			Delim('['),
   387  			Delim('{'), "a", float64(1), Delim('}'),
   388  			Delim('{'), "a", float64(2), Delim('}'),
   389  			Delim(']')}},
   390  		{CaseName: Name(""), json: `{"obj": {"a": 1}}`, expTokens: []any{
   391  			Delim('{'), "obj", Delim('{'), "a", float64(1), Delim('}'),
   392  			Delim('}')}},
   393  		{CaseName: Name(""), json: `{"obj": [{"a": 1}]}`, expTokens: []any{
   394  			Delim('{'), "obj", Delim('['),
   395  			Delim('{'), "a", float64(1), Delim('}'),
   396  			Delim(']'), Delim('}')}},
   397  
   398  		// streaming tokens with intermittent Decode()
   399  		{CaseName: Name(""), json: `{ "a": 1 }`, expTokens: []any{
   400  			Delim('{'), "a",
   401  			decodeThis{float64(1)},
   402  			Delim('}')}},
   403  		{CaseName: Name(""), json: ` [ { "a" : 1 } ] `, expTokens: []any{
   404  			Delim('['),
   405  			decodeThis{map[string]any{"a": float64(1)}},
   406  			Delim(']')}},
   407  		{CaseName: Name(""), json: ` [{"a": 1},{"a": 2}] `, expTokens: []any{
   408  			Delim('['),
   409  			decodeThis{map[string]any{"a": float64(1)}},
   410  			decodeThis{map[string]any{"a": float64(2)}},
   411  			Delim(']')}},
   412  		{CaseName: Name(""), json: `{ "obj" : [ { "a" : 1 } ] }`, expTokens: []any{
   413  			Delim('{'), "obj", Delim('['),
   414  			decodeThis{map[string]any{"a": float64(1)}},
   415  			Delim(']'), Delim('}')}},
   416  
   417  		{CaseName: Name(""), json: `{"obj": {"a": 1}}`, expTokens: []any{
   418  			Delim('{'), "obj",
   419  			decodeThis{map[string]any{"a": float64(1)}},
   420  			Delim('}')}},
   421  		{CaseName: Name(""), json: `{"obj": [{"a": 1}]}`, expTokens: []any{
   422  			Delim('{'), "obj",
   423  			decodeThis{[]any{
   424  				map[string]any{"a": float64(1)},
   425  			}},
   426  			Delim('}')}},
   427  		{CaseName: Name(""), json: ` [{"a": 1} {"a": 2}] `, expTokens: []any{
   428  			Delim('['),
   429  			decodeThis{map[string]any{"a": float64(1)}},
   430  			decodeThis{&SyntaxError{"invalid character '{' after array element", len64(` [{"a": 1} `)}},
   431  		}},
   432  		{CaseName: Name(""), json: `{ "` + strings.Repeat("a", 513) + `" 1 }`, expTokens: []any{
   433  			Delim('{'), strings.Repeat("a", 513),
   434  			decodeThis{&SyntaxError{"invalid character '1' after object key", len64(`{ "` + strings.Repeat("a", 513) + `" `)}},
   435  		}},
   436  		{CaseName: Name(""), json: `{ "\a" }`, expTokens: []any{
   437  			Delim('{'),
   438  			&SyntaxError{"invalid escape sequence `\\a` in string", len64(`{ "`)},
   439  		}},
   440  		{CaseName: Name(""), json: ` \a`, expTokens: []any{
   441  			&SyntaxError{"invalid character '\\\\' looking for beginning of value", len64(` `)},
   442  		}},
   443  		{CaseName: Name(""), json: `,`, expTokens: []any{
   444  			&SyntaxError{"invalid character ',' looking for beginning of value", 0},
   445  		}},
   446  	}
   447  	for _, tt := range tests {
   448  		t.Run(tt.Name, func(t *testing.T) {
   449  			dec := NewDecoder(strings.NewReader(tt.json))
   450  			for i, want := range tt.expTokens {
   451  				var got any
   452  				var err error
   453  
   454  				wantMore := true
   455  				switch want {
   456  				case Delim(']'), Delim('}'):
   457  					wantMore = false
   458  				}
   459  				if got := dec.More(); got != wantMore {
   460  					t.Fatalf("%s:\n\tinput: %s\n\tdec.More() = %v, want %v (next token: %T(%v)) rem:%q", tt.Where, tt.json, got, wantMore, want, want, tt.json[dec.InputOffset():])
   461  				}
   462  
   463  				if dt, ok := want.(decodeThis); ok {
   464  					want = dt.v
   465  					err = dec.Decode(&got)
   466  				} else {
   467  					got, err = dec.Token()
   468  				}
   469  				if errWant, ok := want.(error); ok {
   470  					if err == nil || !reflect.DeepEqual(err, errWant) {
   471  						t.Fatalf("%s:\n\tinput: %s\n\tgot error:  %v\n\twant error: %v", tt.Where, tt.json, err, errWant)
   472  					}
   473  					break
   474  				} else if err != nil {
   475  					t.Fatalf("%s:\n\tinput: %s\n\tgot error:  %v\n\twant error: nil", tt.Where, tt.json, err)
   476  				}
   477  				if !reflect.DeepEqual(got, want) {
   478  					t.Fatalf("%s: token %d:\n\tinput: %s\n\tgot:  %T(%v)\n\twant: %T(%v)", tt.Where, i, tt.json, got, got, want, want)
   479  				}
   480  			}
   481  		})
   482  	}
   483  }
   484  
   485  // Test from golang.org/issue/11893
   486  func TestHTTPDecoding(t *testing.T) {
   487  	const raw = `{ "foo": "bar" }`
   488  
   489  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   490  		w.Write([]byte(raw))
   491  	}))
   492  	defer ts.Close()
   493  	res, err := http.Get(ts.URL)
   494  	if err != nil {
   495  		log.Fatalf("http.Get error: %v", err)
   496  	}
   497  	defer res.Body.Close()
   498  
   499  	foo := struct {
   500  		Foo string
   501  	}{}
   502  
   503  	d := NewDecoder(res.Body)
   504  	err = d.Decode(&foo)
   505  	if err != nil {
   506  		t.Fatalf("Decode error: %v", err)
   507  	}
   508  	if foo.Foo != "bar" {
   509  		t.Errorf(`Decode: got %q, want "bar"`, foo.Foo)
   510  	}
   511  
   512  	// make sure we get the EOF the second time
   513  	err = d.Decode(&foo)
   514  	if err != io.EOF {
   515  		t.Errorf("Decode error:\n\tgot:  %v\n\twant: io.EOF", err)
   516  	}
   517  }
   518  
   519  func TestTokenTruncation(t *testing.T) {
   520  	tests := []struct {
   521  		in  string
   522  		err error
   523  	}{
   524  		{in: ``, err: io.EOF},
   525  		{in: `{`, err: io.EOF},
   526  		{in: `{"`, err: io.ErrUnexpectedEOF},
   527  		{in: `{"k"`, err: io.EOF},
   528  		{in: `{"k":`, err: io.EOF},
   529  		{in: `{"k",`, err: &SyntaxError{"invalid character ',' after object key", int64(len(`{"k"`))}},
   530  		{in: `{"k"}`, err: &SyntaxError{"invalid character '}' after object key", int64(len(`{"k"`))}},
   531  		{in: ` [0`, err: io.EOF},
   532  		{in: `[0.`, err: io.ErrUnexpectedEOF},
   533  		{in: `[0. `, err: &SyntaxError{"invalid character ' ' in numeric literal", int64(len(`[0.`))}},
   534  		{in: `[0,`, err: io.EOF},
   535  		{in: `[0:`, err: &SyntaxError{"invalid character ':' after array element", int64(len(`[0`))}},
   536  		{in: `n`, err: io.ErrUnexpectedEOF},
   537  		{in: `nul`, err: io.ErrUnexpectedEOF},
   538  		{in: `fal `, err: &SyntaxError{"invalid character ' ' in literal false (expecting 's')", int64(len(`fal`))}},
   539  		{in: `false`, err: io.EOF},
   540  	}
   541  	for _, tt := range tests {
   542  		d := NewDecoder(strings.NewReader(tt.in))
   543  		for i := 0; true; i++ {
   544  			if _, err := d.Token(); err != nil {
   545  				if !reflect.DeepEqual(err, tt.err) {
   546  					t.Errorf("`%s`: %d.Token error = %#v, want %v", tt.in, i, err, tt.err)
   547  				}
   548  				break
   549  			}
   550  		}
   551  	}
   552  }
   553  
   554  func TestDecoderInputOffset(t *testing.T) {
   555  	const input = ` [
   556  		[ ] , [ "one" ] , [ "one" , "two" ] ,
   557  		{ } , { "alpha" : "bravo" } , { "alpha" : "bravo" , "fizz" : "buzz" }
   558  	] `
   559  	wantOffsets := []int64{
   560  		0, 1, 2, 5, 6, 7, 8, 9, 12, 13, 18, 19, 20, 21, 24, 25, 30, 31,
   561  		38, 39, 40, 41, 46, 47, 48, 49, 52, 53, 60, 61, 70, 71, 72, 73,
   562  		76, 77, 84, 85, 94, 95, 103, 104, 112, 113, 114, 116, 117, 117,
   563  		117, 117,
   564  	}
   565  	wantMores := []bool{
   566  		true, true, false, true, true, false, true, true, true, false,
   567  		true, false, true, true, true, false, true, true, true, true,
   568  		true, false, false, false, false,
   569  	}
   570  
   571  	d := NewDecoder(strings.NewReader(input))
   572  	checkOffset := func() {
   573  		t.Helper()
   574  		got := d.InputOffset()
   575  		if len(wantOffsets) == 0 {
   576  			t.Fatalf("InputOffset = %d, want nil", got)
   577  		}
   578  		want := wantOffsets[0]
   579  		if got != want {
   580  			t.Fatalf("InputOffset = %d, want %d", got, want)
   581  		}
   582  		wantOffsets = wantOffsets[1:]
   583  	}
   584  	checkMore := func() {
   585  		t.Helper()
   586  		got := d.More()
   587  		if len(wantMores) == 0 {
   588  			t.Fatalf("More = %v, want nil", got)
   589  		}
   590  		want := wantMores[0]
   591  		if got != want {
   592  			t.Fatalf("More = %v, want %v", got, want)
   593  		}
   594  		wantMores = wantMores[1:]
   595  	}
   596  	checkOffset()
   597  	checkMore()
   598  	checkOffset()
   599  	for {
   600  		if _, err := d.Token(); err == io.EOF {
   601  			break
   602  		} else if err != nil {
   603  			t.Fatalf("Token error: %v", err)
   604  		}
   605  		checkOffset()
   606  		checkMore()
   607  		checkOffset()
   608  	}
   609  	checkOffset()
   610  	checkMore()
   611  	checkOffset()
   612  
   613  	if len(wantOffsets)+len(wantMores) > 0 {
   614  		t.Fatal("unconsumed testdata")
   615  	}
   616  }
   617  
   618  func TestDecoderMaxBytesError(t *testing.T) {
   619  	// Verify that Decoder.Decode returns the underlying IO error
   620  	// (not wrapped in *SyntaxError) when http.MaxBytesReader
   621  	// triggers a read limit, matching v1 behavior.
   622  	oversized := strings.Repeat("x", 1<<20+1)
   623  	body := `{"name":"` + oversized + `"}`
   624  
   625  	req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(body))
   626  	rec := httptest.NewRecorder()
   627  	req.Body = http.MaxBytesReader(rec, req.Body, 1<<20)
   628  
   629  	var v map[string]any
   630  	err := NewDecoder(req.Body).Decode(&v)
   631  	if err == nil {
   632  		t.Fatal("expected error, got nil")
   633  	}
   634  
   635  	var maxBytesErr *http.MaxBytesError
   636  	if !errors.As(err, &maxBytesErr) {
   637  		t.Errorf("errors.As(err, *http.MaxBytesError) = false, want true\nerror type: %T\nerror: %v", err, err)
   638  	}
   639  }
   640  

View as plain text