// Package imgcache provides interfaces and types for the image caching proxy. package imgcache import ( "context" "io" "net/url" "time" ) // ImageFormat represents supported output image formats. type ImageFormat string // Supported image output formats. const ( FormatOriginal ImageFormat = "orig" FormatJPEG ImageFormat = "jpeg" FormatPNG ImageFormat = "png" FormatWebP ImageFormat = "webp" FormatAVIF ImageFormat = "avif" FormatGIF ImageFormat = "gif" ) // Size represents requested image dimensions type Size struct { Width int Height int } // OriginalSize returns true if this represents "keep original size" func (s Size) OriginalSize() bool { return s.Width == 0 && s.Height == 0 } // FitMode represents how to fit image into requested dimensions. type FitMode string // Supported image fit modes. const ( FitCover FitMode = "cover" FitContain FitMode = "contain" FitFill FitMode = "fill" FitInside FitMode = "inside" FitOutside FitMode = "outside" ) // ImageRequest represents a request for a processed image type ImageRequest struct { // SourceHost is the origin host (e.g., "cdn.example.com") SourceHost string // SourcePath is the path on the origin (e.g., "/photos/cat.jpg") SourcePath string // SourceQuery is the optional query string for the origin URL SourceQuery string // Size is the requested output dimensions Size Size // Format is the requested output format Format ImageFormat // Quality is the output quality (1-100) for lossy formats Quality int // FitMode is how to fit the image into requested dimensions FitMode FitMode // Signature is the HMAC signature for non-whitelisted hosts Signature string // Expires is the signature expiration timestamp Expires time.Time } // SourceURL returns the full upstream URL to fetch func (r *ImageRequest) SourceURL() string { url := "https://" + r.SourceHost + r.SourcePath if r.SourceQuery != "" { url += "?" + r.SourceQuery } return url } // ImageResponse represents a processed image ready to serve type ImageResponse struct { // Content is the image data reader Content io.ReadCloser // ContentLength is the size in bytes (-1 if unknown) ContentLength int64 // ContentType is the MIME type of the response ContentType string // ETag is the entity tag for caching ETag string // LastModified is when the content was last modified LastModified time.Time // CacheStatus indicates HIT, MISS, or STALE CacheStatus CacheStatus // FetchedBytes is the number of bytes fetched from upstream (0 if cache hit) FetchedBytes int64 } // CacheStatus indicates whether the response was served from cache. type CacheStatus string // Cache status values for response headers. const ( CacheHit CacheStatus = "HIT" CacheMiss CacheStatus = "MISS" CacheStale CacheStatus = "STALE" ) // ImageCache is the main interface for the image caching proxy type ImageCache interface { // Get retrieves a processed image, fetching and processing if necessary Get(ctx context.Context, req *ImageRequest) (*ImageResponse, error) // Warm pre-fetches and caches an image without returning it Warm(ctx context.Context, req *ImageRequest) error // Purge removes a cached image Purge(ctx context.Context, req *ImageRequest) error // Stats returns cache statistics Stats(ctx context.Context) (*CacheStats, error) } // CacheStats contains cache statistics type CacheStats struct { // TotalItems is the number of cached items TotalItems int64 // TotalSizeBytes is the total size of cached content TotalSizeBytes int64 // HitCount is the number of cache hits HitCount int64 // MissCount is the number of cache misses MissCount int64 // HitRate is HitCount / (HitCount + MissCount) HitRate float64 } // SignatureValidator validates request signatures type SignatureValidator interface { // Validate checks if the signature is valid for the request Validate(req *ImageRequest) error // Generate creates a signature for a request Generate(req *ImageRequest) string } // Whitelist checks if a URL is whitelisted (no signature required) type Whitelist interface { // IsWhitelisted returns true if the URL doesn't require a signature IsWhitelisted(u *url.URL) bool } // Fetcher fetches images from upstream origins type Fetcher interface { // Fetch retrieves an image from the origin Fetch(ctx context.Context, url string) (*FetchResult, error) } // FetchResult contains the result of fetching from upstream type FetchResult struct { // Content is the raw image data Content io.ReadCloser // ContentLength is the size in bytes (-1 if unknown) ContentLength int64 // ContentType is the MIME type from upstream ContentType string // Headers contains all response headers from upstream Headers map[string][]string } // Processor handles image transformation (resize, format conversion) type Processor interface { // Process transforms an image according to the request Process(ctx context.Context, input io.Reader, req *ImageRequest) (*ProcessResult, error) // SupportedInputFormats returns MIME types this processor can read SupportedInputFormats() []string // SupportedOutputFormats returns formats this processor can write SupportedOutputFormats() []ImageFormat } // ProcessResult contains the result of image processing type ProcessResult struct { // Content is the processed image data Content io.ReadCloser // ContentLength is the size in bytes ContentLength int64 // ContentType is the MIME type of the output ContentType string // Width is the output image width Width int // Height is the output image height Height int // InputWidth is the original image width before processing InputWidth int // InputHeight is the original image height before processing InputHeight int // InputFormat is the detected input format (e.g., "jpeg", "png") InputFormat string } // Storage handles persistent storage of cached content type Storage interface { // Store saves content and returns its hash Store(ctx context.Context, content io.Reader) (hash string, err error) // Load retrieves content by hash Load(ctx context.Context, hash string) (io.ReadCloser, error) // Delete removes content by hash Delete(ctx context.Context, hash string) error // Exists checks if content exists Exists(ctx context.Context, hash string) (bool, error) }