feat(enhancement): add resetter interface, call reset appropriately, improve code, and test#1124
Conversation
There was a problem hiding this comment.
Pull request overview
This PR enhances streaming/decoding utilities by introducing a reset-capable interface for readers, tightening decoder behavior to avoid infinite streams, and expanding test coverage around JSON/XML decoding and pooled decompressors.
Changes:
- Add a decode object-count limit for JSON/XML decoding to prevent unbounded streams.
- Introduce a
resetterinterface and implementReset()forlimitReadCloser; broadennopReadCloserto support resetting more underlying reader types. - Expand/organize stream-related tests, including new decode scenarios and reset behavior.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| stream.go | Adds decode limits, introduces resetter + limitReadCloser.Reset, and updates nopReadCloser reset behavior. |
| stream_test.go | Refactors JSON decode tests into subtests, adds XML decode tests, adds resetter test, and adds decode-limit tests. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| func decodeXML(r io.Reader, v any) error { | ||
| dec := xml.NewDecoder(r) | ||
| for { | ||
| if err := dec.Decode(v); err == io.EOF { | ||
| break | ||
| } else if err != nil { | ||
| for range maxDecodeObjects { | ||
| if err := dec.Decode(v); err != nil { | ||
| if err == io.EOF { | ||
| return nil | ||
| } | ||
| return err | ||
| } | ||
| } | ||
| return nil | ||
| return fmt.Errorf("resty: XML decode exceeded %d objects without EOF", maxDecodeObjects) | ||
| } |
| func (l *limitReadCloser) Read(p []byte) (n int, err error) { | ||
| if l.l == 0 { | ||
| switch { | ||
| case l.l <= unlimitedRead: | ||
| n, err = l.r.Read(p) | ||
| l.t += int64(n) | ||
| l.f(l.t) | ||
| return n, err | ||
| default: | ||
| if l.t >= l.l { | ||
| return 0, ErrReadExceedsThresholdLimit | ||
| } | ||
| n, err = l.r.Read(p) | ||
| l.t += int64(n) | ||
| l.f(l.t) | ||
| return n, err | ||
| } |
| t.Run("exceeds maxDecodeObjects limit", func(t *testing.T) { | ||
| // Build a reader that returns maxDecodeObjects+1 objects without EOF | ||
| // by using a custom reader that signals no EOF until asked enough times. | ||
| // Simplest approach: patch the limit via the loop by creating a reader | ||
| // backed by a sufficient number of elements. Since maxDecodeObjects is 1M, | ||
| // we instead test the boundary by constructing exactly that many elements | ||
| // with a streaming reader built from io.MultiReader. | ||
| elem := []byte(`{"key": "value"}`) | ||
| readers := make([]io.Reader, maxDecodeObjects+1) | ||
| for i := range readers { | ||
| readers[i] = bytes.NewReader(elem) | ||
| } | ||
| r := io.MultiReader(readers...) | ||
|
|
||
| var v map[string]any | ||
| err := decodeJSON(r, &v) | ||
| assertNotNil(t, err) | ||
| assertEqual(t, "resty: JSON decode exceeded 1000000 objects without EOF", err.Error()) | ||
| }) |
| t.Run("exceeds maxDecodeObjects limit", func(t *testing.T) { | ||
| // Build a reader that returns maxDecodeObjects+1 objects without EOF | ||
| // by using a custom reader that signals no EOF until asked enough times. | ||
| // Simplest approach: patch the limit via the loop by creating a reader | ||
| // backed by a sufficient number of elements. Since maxDecodeObjects is 1M, | ||
| // we instead test the boundary by constructing exactly that many elements | ||
| // with a streaming reader built from io.MultiReader. | ||
| elem := []byte(`<Item><name>x</name></Item>`) | ||
| readers := make([]io.Reader, maxDecodeObjects+1) | ||
| for i := range readers { | ||
| readers[i] = bytes.NewReader(elem) | ||
| } | ||
| r := io.MultiReader(readers...) | ||
|
|
||
| var v Item | ||
| err := decodeXML(r, &v) | ||
| assertNotNil(t, err) | ||
| assertEqual(t, "resty: XML decode exceeded 1000000 objects without EOF", err.Error()) | ||
| }) |
| func doDecodeJSON(dec *json.Decoder, v any) error { | ||
| // Decode all JSON objects in the data | ||
| for { | ||
| if err := dec.Decode(v); err == io.EOF { | ||
| break | ||
| } else if err != nil { | ||
| for range maxDecodeObjects { | ||
| if err := dec.Decode(v); err != nil { | ||
| if err == io.EOF { | ||
| return nil | ||
| } | ||
| return err | ||
| } | ||
| } | ||
| return nil | ||
| return fmt.Errorf("resty: JSON decode exceeded %d objects without EOF", maxDecodeObjects) | ||
| } |
There was a problem hiding this comment.
@copilot for range starts from 0 similar to for loop
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## v3 #1124 +/- ##
==========================================
- Coverage 99.81% 99.74% -0.08%
==========================================
Files 19 19
Lines 3838 3850 +12
==========================================
+ Hits 3831 3840 +9
- Misses 4 6 +2
- Partials 3 4 +1
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
…safeguard, improve code, and test
0e3b358 to
614260f
Compare
No description provided.