gogcli/internal/googleapi/errors.go
salmonumbrella d2be673d10
feat: add retry logic, concurrent fetching, and extended commands
Resilience:
- RetryTransport with circuit breaker for 429/5xx resilience
- Exponential backoff with jitter, respects Retry-After headers
- Circuit breaker auto-resets after 30s of successful requests

Performance:
- Concurrent gmail thread fetching (fixes N+1 query pattern)
- Bounded concurrency with semaphore (max 10 parallel)

New calendar commands:
- colors: list available event/calendar colors
- conflicts: check availability across calendars
- search: find events by text query
- time: show current time in multiple timezones

New gmail commands:
- autoforward: get/enable/disable auto-forwarding
- delegates: list/add/remove mail delegation
- filters: list/create/delete inbox filters
- forwarding: manage forwarding addresses
- sendas: manage send-as aliases
- vacation: get/enable/disable vacation responder
- batch: bulk operations (mark-read, archive, label, delete)
- watch: Pub/Sub push with webhook forwarding

New services:
- Sheets: read/write/append spreadsheet data
- Tasks: manage tasklists and tasks

Developer experience:
- Shell completion (bash, zsh, fish, powershell)
- version command with build info
- --debug flag for verbose logging
- lefthook for pre-commit hooks

Documentation:
- Expanded README with examples
- Gmail watch/Pub/Sub guide (docs/watch.md)
- Architecture spec (docs/spec.md)
- Release process (docs/RELEASING.md)
2025-12-24 17:51:21 -08:00

116 lines
2.8 KiB
Go

package googleapi
import (
"errors"
"fmt"
"time"
)
type AuthRequiredError struct {
Service string
Email string
Cause error
}
func (e *AuthRequiredError) Error() string {
return fmt.Sprintf("auth required for %s %s", e.Service, e.Email)
}
func (e *AuthRequiredError) Unwrap() error {
return e.Cause
}
// RateLimitError indicates rate limit was exceeded
type RateLimitError struct {
RetryAfter time.Duration
Retries int
}
func (e *RateLimitError) Error() string {
if e.RetryAfter > 0 {
return fmt.Sprintf("rate limit exceeded, retry after %s (attempted %d retries)", e.RetryAfter, e.Retries)
}
return fmt.Sprintf("rate limit exceeded after %d retries", e.Retries)
}
// CircuitBreakerError indicates the circuit breaker is open
type CircuitBreakerError struct{}
func (e *CircuitBreakerError) Error() string {
return "circuit breaker is open, too many recent failures - try again later"
}
// QuotaExceededError indicates API quota was exceeded
type QuotaExceededError struct {
Resource string
}
func (e *QuotaExceededError) Error() string {
if e.Resource != "" {
return fmt.Sprintf("API quota exceeded for %s", e.Resource)
}
return "API quota exceeded"
}
// NotFoundError indicates the requested resource was not found
type NotFoundError struct {
Resource string
ID string
}
func (e *NotFoundError) Error() string {
if e.ID != "" {
return fmt.Sprintf("%s not found: %s", e.Resource, e.ID)
}
return fmt.Sprintf("%s not found", e.Resource)
}
// PermissionDeniedError indicates insufficient permissions
type PermissionDeniedError struct {
Resource string
Action string
}
func (e *PermissionDeniedError) Error() string {
if e.Action != "" {
return fmt.Sprintf("permission denied: cannot %s %s", e.Action, e.Resource)
}
return fmt.Sprintf("permission denied for %s", e.Resource)
}
// IsAuthRequiredError checks if the error is an auth required error
func IsAuthRequiredError(err error) bool {
var e *AuthRequiredError
return errors.As(err, &e)
}
// IsRateLimitError checks if the error is a rate limit error
func IsRateLimitError(err error) bool {
var e *RateLimitError
return errors.As(err, &e)
}
// IsCircuitBreakerError checks if the error is a circuit breaker error
func IsCircuitBreakerError(err error) bool {
var e *CircuitBreakerError
return errors.As(err, &e)
}
// IsQuotaExceededError checks if the error is a quota exceeded error
func IsQuotaExceededError(err error) bool {
var e *QuotaExceededError
return errors.As(err, &e)
}
// IsNotFoundError checks if the error is a not found error
func IsNotFoundError(err error) bool {
var e *NotFoundError
return errors.As(err, &e)
}
// IsPermissionDeniedError checks if the error is a permission denied error
func IsPermissionDeniedError(err error) bool {
var e *PermissionDeniedError
return errors.As(err, &e)
}