ratelimit

package
v1.40.1 Latest Latest
Warning

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

Go to latest
Published: Apr 16, 2026 License: MIT Imports: 11 Imported by: 0

Documentation

Overview

Package ratelimit provides a comprehensive rate limiting system for Hector v2.

Features:

  • Multi-layer time windows (minute, hour, day, week, month)
  • Dual tracking (token count AND request count)
  • Flexible scopes (per-session or per-user)
  • Multiple storage backends (in-memory and SQL)
  • Atomic check-and-record operations
  • Detailed usage statistics

Basic Usage

// Create store (memory or SQL)
store := ratelimit.NewMemoryStore()

// Create limiter with config
limiter, err := ratelimit.NewRateLimiter(config, store)

// Check and record usage
result, err := limiter.CheckAndRecord(ctx, ratelimit.ScopeSession, "session-123", 1000, 1)
if !result.Allowed {
    // Handle rate limit exceeded
}

Configuration

rate_limiting:
  enabled: true
  scope: "session"  # or "user"
  backend: "memory"  # or "sql"
  limits:
    - type: token
      window: day
      limit: 100000
    - type: count
      window: minute
      limit: 60

Time Windows

  • minute: 60 seconds (burst protection)
  • hour: 60 minutes (short-term limits)
  • day: 24 hours (daily quotas)
  • week: 7 days (weekly budgets)
  • month: 30 days (monthly billing)

Limit Types

  • token: Track token usage (LLM API tokens, cost control)
  • count: Track request count (rate throttling, DDoS protection)

Scopes

  • session: Each session has independent quotas
  • user: All sessions for a user share quotas

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrRateLimitExceeded is returned when a rate limit is exceeded.
	ErrRateLimitExceeded = errors.New("rate limit exceeded")

	// ErrInvalidIdentifier is returned when an identifier is invalid.
	ErrInvalidIdentifier = errors.New("invalid identifier")

	// ErrStoreUnavailable is returned when the store is unavailable.
	ErrStoreUnavailable = errors.New("store unavailable")
)

Common errors.

Functions

func IsRateLimitError

func IsRateLimitError(err error) bool

IsRateLimitError checks if an error is a rate limit error.

func Middleware

func Middleware(cfg MiddlewareConfig) func(http.Handler) http.Handler

Middleware creates an HTTP middleware that enforces rate limits.

func SimpleMiddleware

func SimpleMiddleware(limiter RateLimiter, excludedPaths ...string) func(http.Handler) http.Handler

SimpleMiddleware creates a simple rate limiting middleware. This is a convenience function for common use cases.

Types

type CheckResult

type CheckResult struct {
	// Allowed indicates whether the operation is allowed.
	Allowed bool `json:"allowed"`

	// Reason provides a human-readable reason if denied.
	Reason string `json:"reason,omitempty"`

	// Usages contains current usage for all limits.
	Usages []Usage `json:"usages"`

	// RetryAfter indicates how long to wait before retrying (if denied).
	RetryAfter *time.Duration `json:"retry_after,omitempty"`
}

CheckResult represents the result of a rate limit check.

func GetRateLimitResult

func GetRateLimitResult(err error) *CheckResult

GetRateLimitResult extracts the CheckResult from a rate limit error. Returns nil if the error is not a RateLimitError.

func UsageFromContext

func UsageFromContext(ctx context.Context) *CheckResult

UsageFromContext extracts rate limit usage from the request context.

func (*CheckResult) GetHighestUsagePercentage

func (r *CheckResult) GetHighestUsagePercentage() float64

GetHighestUsagePercentage returns the highest usage percentage across all limits.

func (*CheckResult) GetUsage

func (r *CheckResult) GetUsage(limitType LimitType, window TimeWindow) *Usage

GetUsage returns usage for a specific limit type and window.

func (*CheckResult) IsExceeded

func (r *CheckResult) IsExceeded() bool

IsExceeded returns true if any limit is exceeded.

type Config

type Config struct {
	// Enabled controls whether rate limiting is active.
	Enabled bool

	// Limits defines the rate limit rules.
	Limits []LimitRule
}

Config holds rate limiting configuration.

type DefaultRateLimiter

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

DefaultRateLimiter implements the RateLimiter interface.

func NewRateLimiter

func NewRateLimiter(cfg *Config, store Store) (*DefaultRateLimiter, error)

NewRateLimiter creates a new rate limiter with the given configuration and store.

func (*DefaultRateLimiter) Check

func (rl *DefaultRateLimiter) Check(ctx context.Context, scope Scope, identifier string) (*CheckResult, error)

Check verifies if the operation is allowed without recording usage.

func (*DefaultRateLimiter) CheckAndRecord

func (rl *DefaultRateLimiter) CheckAndRecord(ctx context.Context, scope Scope, identifier string, tokenCount int64, requestCount int64) (*CheckResult, error)

CheckAndRecord checks limits and records usage in a single atomic operation.

func (*DefaultRateLimiter) GetUsage

func (rl *DefaultRateLimiter) GetUsage(ctx context.Context, scope Scope, identifier string) ([]Usage, error)

GetUsage returns current usage statistics for an identifier.

func (*DefaultRateLimiter) IsEnabled

func (rl *DefaultRateLimiter) IsEnabled() bool

IsEnabled returns whether rate limiting is enabled.

func (*DefaultRateLimiter) Record

func (rl *DefaultRateLimiter) Record(ctx context.Context, scope Scope, identifier string, tokenCount int64, requestCount int64) error

Record records actual usage (tokens and/or count).

func (*DefaultRateLimiter) Reset

func (rl *DefaultRateLimiter) Reset(ctx context.Context, scope Scope, identifier string) error

Reset resets usage for an identifier.

func (*DefaultRateLimiter) ResetExpired

func (rl *DefaultRateLimiter) ResetExpired(ctx context.Context, before time.Time) error

ResetExpired removes expired usage records.

func (*DefaultRateLimiter) Store

func (rl *DefaultRateLimiter) Store() Store

Store returns the underlying store (for testing).

type IdentifierFunc

type IdentifierFunc func(r *http.Request) (identifier string, scope Scope)

IdentifierFunc extracts the rate limit identifier from an HTTP request. This function should return the identifier and scope for the request.

type LimitRule

type LimitRule struct {
	// Type is the limit type (token or count).
	Type LimitType

	// Window is the time window for this limit.
	Window TimeWindow

	// Limit is the maximum allowed in the window.
	Limit int64
}

LimitRule defines a single rate limit rule.

type LimitType

type LimitType string

LimitType represents the type of rate limit.

const (
	// LimitTypeToken tracks token usage (LLM API tokens).
	LimitTypeToken LimitType = "token"

	// LimitTypeCount tracks request count.
	LimitTypeCount LimitType = "count"
)

func ParseLimitType

func ParseLimitType(s string) LimitType

ParseLimitType converts a config string to LimitType.

func (LimitType) String

func (t LimitType) String() string

String returns the string representation of the limit type.

type MemoryStore

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

MemoryStore is an in-memory implementation of Store. It is thread-safe and suitable for development, testing, and single-instance deployments.

func NewMemoryStore

func NewMemoryStore() *MemoryStore

NewMemoryStore creates a new in-memory store.

func (*MemoryStore) Close

func (s *MemoryStore) Close() error

Close closes the store.

func (*MemoryStore) DeleteExpired

func (s *MemoryStore) DeleteExpired(ctx context.Context, before time.Time) error

DeleteExpired deletes expired usage records.

func (*MemoryStore) DeleteUsage

func (s *MemoryStore) DeleteUsage(ctx context.Context, scope Scope, identifier string) error

DeleteUsage deletes usage records for an identifier.

func (*MemoryStore) Dump

func (s *MemoryStore) Dump() map[string]interface{}

Dump returns all records as a map (for debugging).

func (*MemoryStore) GetUsage

func (s *MemoryStore) GetUsage(ctx context.Context, scope Scope, identifier string, limitType LimitType, window TimeWindow) (int64, time.Time, error)

GetUsage gets current usage for a specific limit.

func (*MemoryStore) IncrementUsage

func (s *MemoryStore) IncrementUsage(ctx context.Context, scope Scope, identifier string, limitType LimitType, window TimeWindow, amount int64) (int64, time.Time, error)

IncrementUsage increments usage for a specific limit.

func (*MemoryStore) SetUsage

func (s *MemoryStore) SetUsage(ctx context.Context, scope Scope, identifier string, limitType LimitType, window TimeWindow, amount int64, windowEnd time.Time) error

SetUsage sets usage for a specific limit.

func (*MemoryStore) Size

func (s *MemoryStore) Size() int

Size returns the number of records in the store (for testing).

type MiddlewareConfig

type MiddlewareConfig struct {
	// Limiter is the rate limiter to use.
	Limiter RateLimiter

	// IdentifierFunc extracts the identifier and scope from requests.
	// If nil, DefaultIdentifierFunc is used.
	IdentifierFunc IdentifierFunc

	// TokenEstimator estimates token count for a request.
	// If nil, token count is set to 0 (only count-based limiting).
	TokenEstimator func(r *http.Request) int64

	// ExcludedPaths are paths that bypass rate limiting.
	ExcludedPaths []string

	// OnLimited is called when a request is rate limited.
	// If nil, a default JSON error response is sent.
	OnLimited func(w http.ResponseWriter, r *http.Request, result *CheckResult)
}

MiddlewareConfig configures the rate limiting middleware.

type RateLimitError

type RateLimitError struct {
	// Message is a human-readable error message.
	Message string

	// Result contains the detailed rate limit check result.
	Result *CheckResult
}

RateLimitError represents a rate limit error with detailed information.

func NewRateLimitError

func NewRateLimitError(result *CheckResult) *RateLimitError

NewRateLimitError creates a new RateLimitError from a CheckResult.

func (*RateLimitError) Error

func (e *RateLimitError) Error() string

Error returns the error message.

func (*RateLimitError) Unwrap

func (e *RateLimitError) Unwrap() error

Unwrap returns the underlying error.

type RateLimiter

type RateLimiter interface {
	// Check verifies if the operation is allowed without recording usage.
	// Use this when you want to check limits before potentially expensive operations.
	Check(ctx context.Context, scope Scope, identifier string) (*CheckResult, error)

	// Record records actual usage (tokens and/or count).
	// Use this after an operation completes to record the actual usage.
	Record(ctx context.Context, scope Scope, identifier string, tokenCount int64, requestCount int64) error

	// CheckAndRecord checks limits and records usage in a single atomic operation.
	// This is the recommended method for most use cases as it prevents race conditions.
	CheckAndRecord(ctx context.Context, scope Scope, identifier string, tokenCount int64, requestCount int64) (*CheckResult, error)

	// GetUsage returns current usage statistics for an identifier.
	// Returns usage for all configured limits.
	GetUsage(ctx context.Context, scope Scope, identifier string) ([]Usage, error)

	// Reset resets usage for an identifier.
	// Useful for testing or manual quota resets.
	Reset(ctx context.Context, scope Scope, identifier string) error

	// ResetExpired removes expired usage records.
	// Should be called periodically for cleanup.
	ResetExpired(ctx context.Context, before time.Time) error
}

RateLimiter is the main interface for rate limiting.

Implementations must be thread-safe and support concurrent access.

func NewRateLimiterFromConfig

func NewRateLimiterFromConfig(rateLimitCfg *config.RateLimitConfig, pool *config.DBPool, databaseDSN string) (RateLimiter, error)

NewRateLimiterFromConfig creates a RateLimiter using the database. If rate limiting is disabled, returns nil.

func NewRateLimiterFromConfigWithStore

func NewRateLimiterFromConfigWithStore(cfg *config.RateLimitConfig, store Store) (RateLimiter, error)

NewRateLimiterFromConfigWithStore creates a RateLimiter with a custom store. Useful for testing or when you need to share a store across multiple limiters.

type SQLStore

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

SQLStore is a SQL-based implementation of Store. It supports Postgres, MySQL, and SQLite.

func NewSQLStore

func NewSQLStore(db *sql.DB, dialect string) (*SQLStore, error)

NewSQLStore creates a new SQL-based store. Supported dialects: "postgres", "mysql", "sqlite".

func (*SQLStore) Close

func (s *SQLStore) Close() error

Close closes the store. Note: This does NOT close the underlying database connection, as that connection may be shared with other components.

func (*SQLStore) DeleteExpired

func (s *SQLStore) DeleteExpired(ctx context.Context, before time.Time) error

DeleteExpired deletes expired usage records.

func (*SQLStore) DeleteUsage

func (s *SQLStore) DeleteUsage(ctx context.Context, scope Scope, identifier string) error

DeleteUsage deletes usage records for an identifier.

func (*SQLStore) Dialect

func (s *SQLStore) Dialect() string

Dialect returns the SQL dialect (for testing).

func (*SQLStore) GetUsage

func (s *SQLStore) GetUsage(ctx context.Context, scope Scope, identifier string, limitType LimitType, window TimeWindow) (int64, time.Time, error)

GetUsage gets current usage for a specific limit.

func (*SQLStore) IncrementUsage

func (s *SQLStore) IncrementUsage(ctx context.Context, scope Scope, identifier string, limitType LimitType, window TimeWindow, incrementAmount int64) (int64, time.Time, error)

IncrementUsage increments usage for a specific limit.

func (*SQLStore) SetUsage

func (s *SQLStore) SetUsage(ctx context.Context, scope Scope, identifier string, limitType LimitType, window TimeWindow, amount int64, windowEnd time.Time) error

SetUsage sets usage for a specific limit.

type Scope

type Scope string

Scope represents the scope of rate limiting.

const (
	// ScopeSession applies rate limits per session.
	ScopeSession Scope = "session"

	// ScopeUser applies rate limits per user (across all sessions).
	ScopeUser Scope = "user"
)

func DefaultIdentifierFunc

func DefaultIdentifierFunc(r *http.Request) (string, Scope)

DefaultIdentifierFunc extracts the identifier from the request. It uses the session ID header if present, otherwise falls back to remote address.

func ParseScope

func ParseScope(s string) Scope

ParseScope converts a config string to Scope.

func ScopeFromConfig

func ScopeFromConfig(cfg *config.RateLimitConfig) Scope

ScopeFromConfig returns the rate limiting scope from configuration.

type Store

type Store interface {
	// GetUsage gets current usage for a specific limit.
	// Returns the current amount, window end time, and any error.
	// If no usage exists, returns 0 with a new window end time.
	GetUsage(ctx context.Context, scope Scope, identifier string, limitType LimitType, window TimeWindow) (int64, time.Time, error)

	// IncrementUsage increments usage for a specific limit.
	// Returns the new amount, window end time, and any error.
	// If the window has expired, it resets and starts a new window.
	IncrementUsage(ctx context.Context, scope Scope, identifier string, limitType LimitType, window TimeWindow, amount int64) (int64, time.Time, error)

	// SetUsage sets usage for a specific limit.
	// Used for explicit resets or window rollovers.
	SetUsage(ctx context.Context, scope Scope, identifier string, limitType LimitType, window TimeWindow, amount int64, windowEnd time.Time) error

	// DeleteUsage deletes all usage records for an identifier.
	DeleteUsage(ctx context.Context, scope Scope, identifier string) error

	// DeleteExpired deletes all expired usage records.
	// Records with windowEnd before the specified time are deleted.
	DeleteExpired(ctx context.Context, before time.Time) error

	// Close closes the store and releases resources.
	Close() error
}

Store is the persistence layer for rate limit data.

Implementations must be thread-safe and support concurrent access.

type TimeWindow

type TimeWindow string

TimeWindow represents a rate limiting time window.

const (
	// WindowMinute represents a 60-second window (burst protection).
	WindowMinute TimeWindow = "minute"

	// WindowHour represents a 60-minute window (short-term limits).
	WindowHour TimeWindow = "hour"

	// WindowDay represents a 24-hour window (daily quotas).
	WindowDay TimeWindow = "day"

	// WindowWeek represents a 7-day window (weekly budgets).
	WindowWeek TimeWindow = "week"

	// WindowMonth represents a 30-day window (monthly billing).
	WindowMonth TimeWindow = "month"
)

func ParseTimeWindow

func ParseTimeWindow(s string) TimeWindow

ParseTimeWindow converts a config string to TimeWindow.

func (TimeWindow) Duration

func (w TimeWindow) Duration() time.Duration

Duration returns the duration for the time window.

func (TimeWindow) String

func (w TimeWindow) String() string

String returns the string representation of the time window.

type Usage

type Usage struct {
	// LimitType is the type of limit (token or count).
	LimitType LimitType `json:"limit_type"`

	// Window is the time window for this usage.
	Window TimeWindow `json:"window"`

	// Current is the current usage in the window.
	Current int64 `json:"current"`

	// Limit is the maximum allowed in the window.
	Limit int64 `json:"limit"`

	// WindowEnd is when the current window ends.
	WindowEnd time.Time `json:"window_end"`

	// Remaining is the remaining quota in the window.
	Remaining int64 `json:"remaining"`

	// Percentage is the usage percentage (0-100+).
	Percentage float64 `json:"percentage"`
}

Usage represents current usage for a specific limit.

type ValidationError

type ValidationError struct {
	Field   string
	Message string
}

ValidationError represents a configuration validation error.

func NewValidationError

func NewValidationError(field, message string) *ValidationError

NewValidationError creates a new ValidationError.

func (*ValidationError) Error

func (e *ValidationError) Error() string

Error returns the validation error message.

Jump to

Keyboard shortcuts

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