provider

package
v0.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 30, 2026 License: MIT Imports: 6 Imported by: 0

Documentation

Overview

Package provider defines the mail provider interface (authenticate, list, fetch) along with its concrete implementations, e.g. Gmail and IMAP.

Index

Constants

View Source
const (
	DefaultMaxAttempts = 5
	DefaultBaseDelay   = 500 * time.Millisecond
	DefaultMaxDelay    = 30 * time.Second
	DefaultJitter      = 0.5
)

Retry defaults. Providers that need different numbers build their own RetryConfig; zero fields fall back to these.

View Source
const MaxSeenIDs = 2000

MaxSeenIDs bounds the recently-seen message-id set carried in SyncState. The set is FIFO: once it is full, marking a new id evicts the oldest one.

Variables

This section is empty.

Functions

func Retry

func Retry(ctx context.Context, cfg RetryConfig, retryable func(error) bool, op func(context.Context) error) error

Retry runs op until it succeeds, until an error is judged non-retryable, or until cfg.MaxAttempts is exhausted.

retryable decides whether an error is worth another attempt; a nil retryable means every error is retried. Errors that are (or wrap) ctx's error are never retried. On give-up the last error is returned wrapped, so errors.Is/As still see through to the provider's error.

func RetryValue

func RetryValue[T any](ctx context.Context, cfg RetryConfig, retryable func(error) bool, op func(context.Context) (T, error)) (T, error)

RetryValue is Retry for an operation that produces a value.

func RetryableStatus

func RetryableStatus(code int) bool

RetryableStatus reports whether an HTTP status code is worth retrying: 408 (timeout), 429 (rate limited) and any 5xx.

Note for Google APIs: a rate-limited call can also come back as 403 with reason "rateLimitExceeded"/"userRateLimitExceeded". That needs the error body, which this helper does not see, so callers must add that case themselves.

func TrimSeenIDs

func TrimSeenIDs(ids []string) []string

TrimSeenIDs drops the oldest entries until at most MaxSeenIDs remain.

Types

type Fake

type Fake struct {
	// ProviderName is returned by Name(); defaults to "fake".
	ProviderName string
	// Messages are replayed, in order, on every Fetch.
	Messages []RawMessage

	// FetchErr, when non-nil, is returned after the messages have been
	// replayed — the "fetch died partway through" case, with the state
	// accumulated so far still returned.
	FetchErr error
	// FailEarly makes Fetch stop after delivering FailAfter messages and
	// return FetchErr (or a generic error), with the partial state.
	FailEarly bool
	// FailAfter is the number of messages delivered before FailEarly bites.
	FailAfter int
	// NextHistoryID, when non-zero, is written to the returned state.
	NextHistoryID uint64
	// NextExtra entries are merged into the returned state's Extra.
	NextExtra map[string]string
	// Now, when non-zero, is used as the returned LastSyncTime instead of
	// time.Now(), so tests can assert on the persisted state exactly.
	Now time.Time
	// SkipSeen makes Fetch drop messages whose ID is already in
	// state.SeenIDs, mimicking a real provider's dedup.
	SkipSeen bool

	// Observed by tests after the run.
	Calls     int
	LastState SyncState
	Delivered []string
}

Fake is a Provider that replays a canned slice of RawMessages. It exists so other packages (the pipeline, the run command) can be tested end to end without a network or a provider SDK.

A zero Fake is usable: it is a provider named "fake" with no messages.

func NewFake

func NewFake(name string, msgs ...RawMessage) *Fake

NewFake returns a Fake named name that replays msgs.

func (*Fake) Fetch

func (f *Fake) Fetch(ctx context.Context, state SyncState, fn func(RawMessage) error) (SyncState, error)

Fetch implements Provider: it replays Messages through fn, marking each delivered id seen and advancing the state. If fn returns an error the replay stops and the state accumulated so far is returned with that error.

func (*Fake) Name

func (f *Fake) Name() string

Name implements Provider.

type Provider

type Provider interface {
	// Name is the provider's short identifier, e.g. "gmail" or "imap".
	Name() string
	// Fetch streams messages newer than state via fn and returns the updated
	// state. fn returning an error aborts the fetch; the state accumulated up
	// to that point is returned along with the error.
	//
	// Implementations must respect ctx cancellation.
	Fetch(ctx context.Context, state SyncState, fn func(RawMessage) error) (SyncState, error)
}

Provider is the seam that keeps the pipeline provider-agnostic: everything downstream sees RawMessage and SyncState, never a provider SDK type.

type RawMessage

type RawMessage struct {
	// ID is a provider-scoped stable identifier. It is used for dedup and, by
	// the sinks, to derive idempotent filenames, so it must be stable across
	// runs (Gmail: the message id; IMAP: account:mailbox:uidvalidity:uid).
	ID string
	// ThreadID is the provider's own conversation id, when it has one (Gmail:
	// `threadId`, returned on the same users.messages.get response as `raw`, so
	// it costs no extra call). Providers with no native threading (IMAP) leave
	// it empty and model.Parse synthesizes one from the message's threading
	// headers — see model.Message.ThreadID.
	ThreadID string
	// Raw is the complete RFC822 message, byte-faithful.
	Raw []byte
	// InternalDate is the time the provider received the message (not the
	// Date: header, which the sender controls).
	InternalDate time.Time
	// Labels are provider-side labels/folders resolved to human names
	// (Gmail label names; IMAP mailbox names).
	Labels []string
}

RawMessage is one message as the provider delivered it: the untouched RFC822 bytes plus the handful of provider metadata fields the pipeline needs before it has parsed anything.

type RetryConfig

type RetryConfig struct {
	// MaxAttempts is the total number of calls, not the number of retries.
	MaxAttempts int
	// BaseDelay is the delay before the second attempt; it doubles thereafter.
	BaseDelay time.Duration
	// MaxDelay caps the pre-jitter delay.
	MaxDelay time.Duration
	// Jitter is the fraction of each delay that is randomised, in [0,1]. With
	// the default 0.5 the actual delay is uniform in [0.5d, d]. Zero means the
	// default; use a negative value to disable jitter entirely.
	Jitter float64
	// contains filtered or unexported fields
}

RetryConfig tunes exponential backoff with jitter. The zero value is usable and behaves like DefaultRetryConfig.

func DefaultRetryConfig

func DefaultRetryConfig() RetryConfig

DefaultRetryConfig is the backoff every provider should use unless it has a reason not to: 5 attempts, 500ms base, doubling, capped at 30s, 50% jitter.

type SyncState

type SyncState struct {
	// HistoryID is the Gmail users.history cursor. Zero means "no incremental
	// cursor", which forces a full scan.
	HistoryID uint64 `json:"history_id,omitempty"`
	// LastSyncTime is when the last successful fetch completed. Providers that
	// have no cursor of their own use it to bound their query.
	LastSyncTime time.Time `json:"last_sync_time"`
	// Extra is free-form per-provider state. Keys are namespaced by the
	// provider, e.g. the IMAP provider stores one pair per mailbox:
	//
	//	imap.INBOX.uidvalidity = "1650000000"
	//	imap.INBOX.last_uid    = "48213"
	Extra map[string]string `json:"extra,omitempty"`
	// SeenIDs is a bounded FIFO set of recently delivered RawMessage.IDs,
	// oldest first. It is belt-and-braces dedup alongside the sinks' idempotent
	// filenames: providers may skip ids already in it, and the pipeline marks
	// each stored message with MarkSeen.
	SeenIDs []string `json:"seen_ids,omitempty"`
}

SyncState is the per-account cursor a provider hands back after a fetch, so the next run can pick up where this one stopped. It is JSON-serializable and persisted verbatim by internal/state.

Providers should treat fields they do not own as opaque and carry them through unchanged.

func (SyncState) Clone

func (s SyncState) Clone() SyncState

Clone returns a deep copy, so a caller can hand a provider a state it may mutate freely without disturbing the copy on disk.

func (*SyncState) GetExtra

func (s *SyncState) GetExtra(key string) string

GetExtra returns the Extra value for key, or "" when unset.

func (*SyncState) MarkSeen

func (s *SyncState) MarkSeen(id string)

MarkSeen adds id to the recently-seen set, evicting the oldest entries once the set exceeds MaxSeenIDs. Marking an id that is already present is a no-op, so an id keeps its original position in the FIFO.

func (*SyncState) Seen

func (s *SyncState) Seen(id string) bool

Seen reports whether id is in the recently-seen set.

func (*SyncState) SetExtra

func (s *SyncState) SetExtra(key, value string)

SetExtra sets an Extra key, allocating the map when needed. Setting an empty value deletes the key.

Directories

Path Synopsis
Package gmail implements the Gmail provider: OAuth against a user-supplied desktop OAuth client, cached token storage, and (in later files) message fetching via the Gmail API.
Package gmail implements the Gmail provider: OAuth against a user-supplied desktop OAuth client, cached token storage, and (in later files) message fetching via the Gmail API.
Package imap fetches mail from any IMAP4rev1/rev2 server behind internal/provider.Provider.
Package imap fetches mail from any IMAP4rev1/rev2 server behind internal/provider.Provider.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL