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)
116 lines
2.8 KiB
Go
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)
|
|
}
|