Files
go-telegram/docs/reference/client.md
T
lukaszraczylo bfb7e9875e feat(client): opt-in fasthttp transport (NewFastHTTPDoer)
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.
2026-05-10 23:07:04 +01:00

24 KiB

client

import "github.com/lukaszraczylo/go-telegram/client"

Package client provides HTTP client primitives for the Telegram Bot API.

Index

Variables

Sentinel errors returned via APIError.Unwrap when the description matches. Compare with errors.Is.

var (
    ErrUnauthorized       = errors.New("telegram: unauthorized")
    ErrChatNotFound       = errors.New("telegram: chat not found")
    ErrMessageNotModified = errors.New("telegram: message is not modified")
    ErrTooManyRequests    = errors.New("telegram: too many requests")
    ErrBadRequest         = errors.New("telegram: bad request")
    ErrForbidden          = errors.New("telegram: forbidden")
    ErrUserNotFound       = errors.New("telegram: user not found")
    ErrMessageNotFound    = errors.New("telegram: message not found")
)

func Call

func Call[Req any, Resp any](ctx context.Context, b *Bot, method string, req Req) (Resp, error)

Call is the single point through which every Telegram Bot API method invocation flows. It marshals the request, signs the URL with the bot token, dispatches via HTTPDoer, decodes the Result envelope, and translates non-OK responses into typed errors.

It is generic over both request and response types. Methods with no parameters may pass a nil Req; the helper sends "{}" in that case so Telegram receives a syntactically valid empty object.

Call is exported because the api package (which lives outside this one) invokes it from generated method wrappers. User code should not normally call it directly — use the typed wrappers in package api instead.

func CallRaw

func CallRaw[Req any](ctx context.Context, b *Bot, method string, req Req) (json.RawMessage, error)

CallRaw is like Call but returns the raw JSON of the result field instead of decoding it into a typed value. Generated method wrappers for sealed-interface return types (ChatMember, MenuButton, etc.) use this helper, then dispatch through the union's UnmarshalXxx function.

CallRaw still translates non-OK responses into *APIError just like Call.

func NewDefaultHTTPDoer

func NewDefaultHTTPDoer() *http.Client

NewDefaultHTTPDoer returns an *http.Client with sensible defaults for Telegram Bot API usage:

  • 60s overall timeout (longer than typical long-poll Timeout=30s).
  • Connection pooling sized for a small number of long-lived hosts.
  • HTTP/2 enabled (default in net/http).

type APIError

APIError represents a non-OK Telegram Bot API response. It satisfies error and unwraps to a sentinel (ErrUnauthorized, etc.) where the description matches a known prefix, enabling errors.Is checks.

type APIError struct {
    Code        int
    Description string
    Parameters  *ResponseParameters
    // contains filtered or unexported fields
}

func (*APIError) Error

func (e *APIError) Error() string

Error implements error.

func (*APIError) IsRetryable

func (e *APIError) IsRetryable() bool

IsRetryable returns true for transient HTTP statuses (429, 5xx).

func (*APIError) RetryAfter

func (e *APIError) RetryAfter() time.Duration

RetryAfter returns the recommended back-off duration. It honours the Telegram-supplied retry_after parameter; if absent, returns 0.

func (*APIError) Unwrap

func (e *APIError) Unwrap() error

Unwrap returns the matched sentinel error, if any.

type BodyEncoder

BodyEncoder is an optional Codec extension that encodes directly into an io.Writer, skipping the intermediate []byte that Marshal returns. Call uses this when present to avoid allocating the marshal result and the bytes.Reader that wraps it; codecs without it fall through to Marshal + bytes.NewReader.

type BodyEncoder interface {
    MarshalTo(w io.Writer, v any) error
}

type Bot

Bot is the Telegram Bot API client. Construct via New. All API methods (declared in package api) hang off *Bot via thin wrappers around call.

type Bot struct {
    // contains filtered or unexported fields
}

func New

func New(token string, opts ...Option) *Bot

New constructs a Bot with the given token and optional configuration. The default HTTP client is tuned for long-poll workloads (see NewDefaultHTTPDoer); the default codec wraps encoding/json; the default logger discards records.

func (*Bot) BaseURL

func (b *Bot) BaseURL() string

BaseURL returns the configured Telegram API base URL.

func (*Bot) Codec

func (b *Bot) Codec() Codec

Codec returns the configured Codec.

func (*Bot) HTTP

func (b *Bot) HTTP() HTTPDoer

HTTP returns the underlying HTTPDoer. Exposed for adapters that need to share connection pools or for diagnostic checks.

func (*Bot) Logger

func (b *Bot) Logger() Logger

Logger returns the configured Logger.

func (*Bot) Token

func (b *Bot) Token() string

Token returns the bot token. Exposed for advanced use cases (custom transports, manual URL building); ordinary code does not need it.

type Codec

Codec encodes/decodes JSON payloads exchanged with the Telegram Bot API. The default implementation wraps goccy/go-json. Users may plug in bytedance/sonic or any compatible encoder by passing WithCodec to New.

type Codec interface {
    Marshal(v any) ([]byte, error)
    Unmarshal(data []byte, v any) error
}

type DefaultCodec

DefaultCodec wraps goccy/go-json. It is the zero-value safe default.

type DefaultCodec struct{}

func (DefaultCodec) Marshal

func (DefaultCodec) Marshal(v any) ([]byte, error)

Marshal calls json.Marshal.

func (DefaultCodec) MarshalTo

func (DefaultCodec) MarshalTo(w io.Writer, v any) error

MarshalTo encodes v into w via goccy/go-json's streaming encoder. The trailing newline that Encoder appends is valid JSON whitespace and is accepted by Telegram's parser.

func (DefaultCodec) Unmarshal

func (DefaultCodec) Unmarshal(data []byte, v any) error

Unmarshal calls json.Unmarshal.

type FastHTTPDoer

FastHTTPDoer is an HTTPDoer backed by github.com/valyala/fasthttp. It trades net/http compatibility (and HTTP/2 support) for substantially fewer allocations per request — fasthttp pools its Request and Response objects and uses a zero-allocation HTTP/1.1 parser.

Use it for high-throughput bots when GC pressure matters and you don't need HTTP/2 or any net/http-only middleware (RoundTripper composition, the OpenTelemetry httptrace family, etc.):

bot := client.New(token, client.WithHTTPClient(client.NewFastHTTPDoer()))

Wrap with RetryDoer the same way you would the default doer.

type FastHTTPDoer struct {
    // contains filtered or unexported fields
}

func NewFastHTTPDoer

func NewFastHTTPDoer(opts ...FastHTTPDoerOption) *FastHTTPDoer

NewFastHTTPDoer constructs a FastHTTPDoer with sensible defaults.

func (*FastHTTPDoer) Do

func (d *FastHTTPDoer) Do(req *http.Request) (*http.Response, error)

Do satisfies HTTPDoer by translating req into a pooled fasthttp.Request, dispatching it, and returning a *http.Response whose Body releases the pooled fasthttp.Response when Close is called.

The conversion is intentionally minimal: URL goes via req.URL.RequestURI() + Host (avoids re-parsing), header values move byte-for-byte, and the body is taken straight from req.Body. *bytes.Buffer / *bytes.Reader are recognised so we can pass the underlying bytes without io.ReadAll.

type FastHTTPDoerOption

FastHTTPDoerOption configures a FastHTTPDoer.

type FastHTTPDoerOption func(*FastHTTPDoer)

func WithFastHTTPClient

func WithFastHTTPClient(c *fasthttp.Client) FastHTTPDoerOption

WithFastHTTPClient swaps in a pre-configured *fasthttp.Client. Useful for sharing a connection pool across multiple bots or applying custom dial / TLS configuration.

func WithFastHTTPReadTimeout

func WithFastHTTPReadTimeout(t time.Duration) FastHTTPDoerOption

WithFastHTTPReadTimeout sets the per-request fallback timeout used when the inbound context has no deadline. Long-poll callers should pass a value larger than the long-poll timeout.

type HTTPDoer

HTTPDoer abstracts the HTTP transport. The default is a net/http client tuned for Telegram's long-poll usage. Users may plug in valyala/fasthttp (via an adapter), or any custom retry/circuit-breaker client by passing WithHTTPClient to New.

type HTTPDoer interface {
    Do(req *http.Request) (*http.Response, error)
}

type Logger

Logger is a slog-shaped logging interface. Users pass any compatible implementation via WithLogger. The default is NoopLogger, which discards everything.

type Logger interface {
    Debug(msg string, attrs ...any)
    Info(msg string, attrs ...any)
    Warn(msg string, attrs ...any)
    Error(msg string, attrs ...any)
}

type MultipartFile

MultipartFile describes a single file part in a multipart upload.

type MultipartFile struct {
    FieldName string
    Filename  string
    Reader    io.Reader
}

type NetworkError

NetworkError wraps a transport-level failure (DNS, TCP, TLS, timeout short of an HTTP response).

type NetworkError struct{ Err error }

func (*NetworkError) Error

func (e *NetworkError) Error() string

func (*NetworkError) Unwrap

func (e *NetworkError) Unwrap() error

type NoopLogger

NoopLogger discards all log records. It is the zero-value safe default.

type NoopLogger struct{}

func (NoopLogger) Debug

func (NoopLogger) Debug(string, ...any)

func (NoopLogger) Error

func (NoopLogger) Error(string, ...any)

func (NoopLogger) Info

func (NoopLogger) Info(string, ...any)

func (NoopLogger) Warn

func (NoopLogger) Warn(string, ...any)

type Option

Option configures a Bot at construction time. Per-call configuration is expressed via typed parameter structs (e.g. SendMessageParams), not options.

type Option func(*Bot)

func WithBaseURL

func WithBaseURL(url string) Option

WithBaseURL overrides the API base URL. Useful for testing against a local httptest.Server, or for self-hosted Bot API servers.

func WithCodec

func WithCodec(c Codec) Option

WithCodec overrides the JSON codec. Pass goccy/go-json, sonic, or any type implementing Codec to swap out encoding/json.

func WithHTTPClient

func WithHTTPClient(c HTTPDoer) Option

WithHTTPClient overrides the HTTP transport. Pass any HTTPDoer implementation (e.g. an *http.Client wrapping a custom RoundTripper, or a fasthttp adapter).

func WithLogger

func WithLogger(l Logger) Option

WithLogger sets the logger used for diagnostic events. Passing nil silently disables logging.

type ParseError

ParseError wraps a JSON decode failure on a response body. Body is retained (truncated to 4 KiB); Error() displays up to 256 bytes for diagnostics.

type ParseError struct {
    Err  error
    Body []byte
}

func (*ParseError) Error

func (e *ParseError) Error() string

func (*ParseError) Unwrap

func (e *ParseError) Unwrap() error

type ResponseParameters

ResponseParameters is the optional metadata Telegram includes on certain failures. The most common is RetryAfter (seconds) on 429 responses.

This type is duplicated in package api for users; keeping a copy here avoids an import cycle (api imports client, not vice versa).

type ResponseParameters struct {
    MigrateToChatID int64 `json:"migrate_to_chat_id,omitempty"`
    RetryAfter      int   `json:"retry_after,omitempty"`
}

type Result

Result is the universal Telegram API response envelope. Every successful response is shaped {"ok":true,"result":T,...}; failure responses set ok to false and populate ErrorCode / Description / Parameters.

Result is generic over T so generated method wrappers can decode the strongly-typed payload directly. Users do not normally construct or inspect Result values; method wrappers unwrap them and return either the typed payload or a *APIError.

type Result[T any] struct {
    OK          bool                `json:"ok"`
    Result      T                   `json:"result,omitempty"`
    ErrorCode   int                 `json:"error_code,omitempty"`
    Description string              `json:"description,omitempty"`
    Parameters  *ResponseParameters `json:"parameters,omitempty"`
}

type RetryDoer

RetryDoer is an HTTPDoer that retries transient failures (429, 5xx, and network errors) with exponential backoff. It honours the retry_after value Telegram supplies on rate-limit responses.

Wrap any HTTPDoer to add retry behaviour:

bot := client.New(token, client.WithHTTPClient(
    client.NewRetryDoer(client.NewDefaultHTTPDoer())))
type RetryDoer struct {
    // contains filtered or unexported fields
}

func NewRetryDoer

func NewRetryDoer(inner HTTPDoer, opts ...RetryOption) *RetryDoer

NewRetryDoer wraps inner with retry behaviour.

func (*RetryDoer) Do

func (d *RetryDoer) Do(req *http.Request) (*http.Response, error)

Do dispatches via the inner HTTPDoer and retries on transient failures. The request body is buffered on first attempt so it can be replayed.

type RetryOption

RetryOption configures a RetryDoer.

type RetryOption func(*RetryDoer)

func WithBackoffFactor

func WithBackoffFactor(f float64) RetryOption

WithBackoffFactor sets the exponential growth factor. Default 2.0.

func WithBaseBackoff

func WithBaseBackoff(d time.Duration) RetryOption

WithBaseBackoff sets the initial backoff duration. Default 500ms.

func WithJitter

func WithJitter(j float64) RetryOption

WithJitter sets the jitter fraction (0..1) applied to each backoff. Default 0.2.

func WithMaxAttempts

func WithMaxAttempts(n int) RetryOption

WithMaxAttempts sets the maximum number of attempts (including the initial one). Default 4 (one initial + three retries).

func WithMaxBackoff

func WithMaxBackoff(d time.Duration) RetryOption

WithMaxBackoff caps the backoff at max. Default 30s.

Generated by gomarkdoc