Adds an alternative HTTPDoer backed by valyala/fasthttp for high-throughput
bots. Cuts per-call allocs from 102 to 56 in the cross-library bench
(within 8 of telego, which uses fasthttp by default), and per-call bytes
from 11.1 KiB to 6.6 KiB.
bot := client.New(token,
client.WithHTTPClient(client.NewFastHTTPDoer()),
)
Implementation notes:
- Wraps *fasthttp.Client behind the existing HTTPDoer (Do *http.Request)
interface, so RetryDoer, custom transports, observability middleware,
and the 1428 generated tests all keep working as-is.
- Translates *http.Request -> fasthttp.Request once per call and
returns a *http.Response whose Body releases the pooled fasthttp
response on Close (net/http contract).
- Recognises the bufferReadCloser / readerReadCloser shapes produced
by buildRequest and passes their underlying bytes straight to
SetBodyRaw -- no io.ReadAll, no copy.
- Honours ctx.Deadline via DoDeadline, falls back to WithFastHTTPReadTimeout
when no deadline is set. fasthttp.ErrTimeout maps to
context.DeadlineExceeded for errors.Is compatibility.
Default stays net/http: fasthttp is HTTP/1.1 only, doesn't compose with
the http.RoundTripper middleware ecosystem, and most users don't have
the throughput to notice. Bots making thousands of API calls/sec should
opt in.
Multipart/file-upload path remains on net/http per the agreed scope --
the perf bottleneck was JSON-method round-trip, not file uploads.
Time numbers in the report deferred until a quiet-system bench run;
allocs/bytes numbers (which are deterministic per code path) are
already updated.
Two changes that together cut allocs/call from 15 to 13 (client-internal
bench) and per-call CPU from 600ns to 455ns (-24%) on the no-HTTP path:
1. Codec gets an optional BodyEncoder extension (MarshalTo io.Writer).
When present, encodeJSONBody stream-encodes the request directly into
a pooled *bytes.Buffer instead of allocating a [2-step] Marshal+Reader
pair. DefaultCodec implements it via goccy/go-json.NewEncoder.
2. *Bot caches the parsed base URL on construction. buildRequest skips
net/http.NewRequestWithContext for the common case and constructs
*http.Request manually — clones the URL by value, sets the method
path, and populates ContentLength + GetBody from the body's concrete
type so RetryDoer's body-replay across attempts still works.
Cross-library bench (sendMessage round-trip vs httptest.Server): -2
allocs/call (104 -> 102), bytes -1.2%, time within noise (real HTTP
plumbing dominates). The CPU win is visible on synthetic stub-doer
benches and translates to lower GC pressure on sustained-throughput
workloads.
Slow-path fallback preserved for codecs that don't implement BodyEncoder
and for *Bot instances where url.Parse on the configured base failed —
they take the original NewRequestWithContext path.
GetChatAdministrators returns []ChatMember, where ChatMember is a
sealed-interface union. The codegen template emitted the generic
client.Call[..., []ChatMember] for it — encoding/json cannot unmarshal
a slice of an interface (no discriminator-aware path), so every real
response from Telegram failed at the parse step:
telegram: parse: json: cannot unmarshal api.ChatMember into
Go struct field Result[[]ChatMember].Result of type api.ChatMember
Fix is in cmd/genapi/methods.tmpl: add a third branch alongside the
existing single-union branch. When a method returns []<union>,
emit CallRaw + json.Unmarshal into []json.RawMessage + per-element
Unmarshal<Union>(e). Mirrors what GetChatMember (single-element)
already does, applied uniformly so any future slice-of-union method
Telegram introduces inherits the right shape.
Survey of v1.1.1 across all 23 sealed-interface unions confirms
GetChatAdministrators was the only broken site; the fix regenerates
just that one method body. New regression tests in
api/getchatadministrators_test.go cover the typical
admin+owner response and the empty-array case.
- Add gomarkdoc-driven reference docs in docs/reference/, regenerated
automatically by 'make regen' alongside the api/ codegen
- New 'make docs' target installs gomarkdoc on first run; 'make
docs-check' is a CI gate
- Fold doc-clean assertion into existing codegen-clean job (single
diff check covers spec + api + reference)
- Rewrite README header: logo via <picture>, friendlier tagline,
emoji-led 'Why you'll like it' bullets instead of Why-table
- Drop duplicate echo snippet, soften 'Codegen pipeline' section into
'Keeping up with Telegram'
- Link reference from README, Pages nav, and a new Markdown reference
card on index.html (target = GitHub source view, renders .md natively)