refactor RequestParseForm and achieve greatness in API design

This commit is contained in:
Patricio Whittingslow
2026-07-30 00:59:11 -03:00
parent 1985688076
commit 7503e88cb1
8 changed files with 245 additions and 91 deletions
+8
View File
@@ -22,6 +22,10 @@ func (f *Form) EnableBufferGrowth(enableGrowth bool) { f.kv.EnableBufferGrowth(e
// Reset discards parsed pairs and sets the buffer to parse in place.
// If buf is nil the current buffer is reused.
//
// capKV sizes the pair table. With growth disabled it is a hard limit, so a
// capKV of 0 leaves no room for a single pair and [Form.Parse] answers
// [ErrBufferExhausted]; size it to the pairs expected.
func (f *Form) Reset(buf []byte, capKV int) {
f.kv.Reset(buf, capKV)
}
@@ -29,6 +33,10 @@ func (f *Form) Reset(buf []byte, capKV int) {
// ReadFromBytes appends buf to the underlying buffer, accumulating data to parse. Returns ErrBufferExhausted when buf does not fit and growth is disabled.
func (f *Form) ReadFromBytes(b []byte) error { return f.kv.ReadFromBytes(b) }
// BufferUsed returns bytes accumulated by the Read* methods and awaiting a
// [Form.Parse]. See [kvBuffer.BufferUsed].
func (f *Form) BufferUsed() int { return f.kv.BufferUsed() }
// ReadLimited appends at most limit bytes read from r to the underlying buffer. A read returning data alongside io.EOF reports a nil error, later ones io.EOF.
func (f *Form) ReadLimited(r io.Reader, limit int) (int, error) { return f.kv.ReadLimited(r, limit) }
+41
View File
@@ -155,3 +155,44 @@ func TestFormParseReuseNoAlloc(t *testing.T) {
t.Errorf("reused Form allocated %v times, want 0", allocs)
}
}
// BufferUsed reports buffered bytes, not parsed pairs, so a caller appending
// from several sources can tell whether a separator is needed before the next
// one. Form.Len is zero until Parse runs and cannot answer that.
func TestFormBufferUsed(t *testing.T) {
var f Form
f.Reset(nil, 0)
if got := f.BufferUsed(); got != 0 {
t.Errorf("want 0 on a fresh form, got %d", got)
}
if err := f.ReadFromBytes([]byte("a=1")); err != nil {
t.Fatal(err)
}
if got := f.BufferUsed(); got != 3 {
t.Errorf("want 3 buffered, got %d", got)
}
if got := f.Len(); got != 0 {
t.Errorf("Len must stay 0 until Parse, got %d", got)
}
// A second source appended behind a separator.
if err := f.ReadFromBytes([]byte("&b=2")); err != nil {
t.Fatal(err)
}
if got := f.BufferUsed(); got != 7 {
t.Errorf("want 7 buffered, got %d", got)
}
if err := f.Parse(); err != nil {
t.Fatal(err)
}
if got := render(&f); got != "a=1|b=2" {
t.Errorf("want a=1|b=2, got %q", got)
}
if got := f.BufferUsed(); got != 7 {
t.Errorf("BufferUsed must not change on Parse, got %d", got)
}
// Reset discards the pairs and the buffered bytes with them.
f.Reset(nil, 0)
if got := f.BufferUsed(); got != 0 {
t.Errorf("want 0 after Reset, got %d", got)
}
}
+5
View File
@@ -23,6 +23,11 @@ func (kvb *kvBuffer) free() int { return cap(kvb.buf) - len(kvb.buf) }
// Stored pairs alias it, so writing to it mangles them.
func (kvb *kvBuffer) BufferRaw() []byte { return kvb.buf }
// BufferUsed returns the raw memory used, which is what a caller appending from
// several sources checks to know whether a separator is needed. Counts buffered
// bytes and not parsed pairs, so it is set before a Parse and unchanged by one.
func (kvb *kvBuffer) BufferUsed() int { return len(kvb.buf) }
// EnableBufferGrowth allows the buffer to grow past the memory [kvBuffer.Reset]
// was handed. The setting outlives Reset; with growth off callers get [ErrBufferExhausted].
func (kvb *kvBuffer) EnableBufferGrowth(enableGrowth bool) {