Files
go-telegram/docs/reference/client.md
T
lukaszraczylo 75c7ce3119 perf(client): pool req-body buffer + manual http.Request with cached URL
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.
2026-05-10 22:36:57 +01:00

21 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 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