One image request type instead of five (closes #307)
check / check (push) Canceled after 0s

The new package internal/imagerequest holds the request type, its two
parts Source and Transform, the Format and Fit types, the dimension and
fit checks, the /v1/image/ path parsing and the variant key; it imports
no other package of pixa. imgcache.ParsedURL, imgcache.ImageRequest,
signature.Request, imageprocessor.Request, the second Size, format and
fit types and the conversions between them are gone. The image
processor takes a Transform only. encurl keeps a private struct with the
token's short CBOR field names. VariantKey moves with the function that
makes it. Tests follow the moved and renamed code; the tests of the
removed conversions go with them.

Model: opus-5-5
This commit was merged in pull request #323.
This commit is contained in:
2026-10-21 14:41:07 +02:00
parent 385c0ca76f
commit 00d8ba26c5
68 changed files with 2217 additions and 1978 deletions
+11 -39
View File
@@ -10,8 +10,9 @@ import (
"log/slog"
"os"
"path/filepath"
"strings"
"time"
"sneak.berlin/go/pixa/internal/imagerequest"
)
// Storage constants.
@@ -32,9 +33,6 @@ var (
// ContentHash is a SHA256 hash of file content (hex-encoded).
type ContentHash string
// VariantKey is a SHA256 hash identifying a specific image variant (hex-encoded).
type VariantKey string
// PathHash is a SHA256 hash of a URL path (hex-encoded).
type PathHash string
@@ -403,34 +401,6 @@ func HashPath(path string) PathHash {
return PathHash(hex.EncodeToString(h[:]))
}
// CacheKey generates a unique key for a request variant.
// Format: sha256(host:path:query:width:height:format:quality:fit_mode)
// The path and query go through escapePercentAndColon, so that a ":" in
// either cannot be taken for the one between them.
func CacheKey(req *ImageRequest) VariantKey {
data := fmt.Sprintf("%s:%s:%s:%d:%d:%s:%d:%s",
req.SourceHost,
escapePercentAndColon(req.SourcePath),
escapePercentAndColon(req.SourceQuery),
req.Size.Width,
req.Size.Height,
req.Format,
req.Quality,
req.FitMode,
)
h := sha256.Sum256([]byte(data))
return VariantKey(hex.EncodeToString(h[:]))
}
// escapePercentAndColon returns s with each "%" written as "%25" and each
// ":" as "%3A", so that the result holds no ":".
func escapePercentAndColon(s string) string {
s = strings.ReplaceAll(s, "%", "%25")
return strings.ReplaceAll(s, ":", "%3A")
}
// VariantStorage handles key-based file storage for processed image variants.
// Files are stored at: <basedir>/<ab>/<cd>/<cache_key>
// Metadata is stored at: <basedir>/<ab>/<cd>/<cache_key>.meta
@@ -464,7 +434,7 @@ func NewVariantStorage(baseDir string, log *slog.Logger) (*VariantStorage, error
// with writeFileSynced. The .meta file is written first, so that a crash
// cannot leave the content in place without it.
func (s *VariantStorage) Store(
key VariantKey, r io.Reader, contentType string,
key imagerequest.VariantKey, r io.Reader, contentType string,
) (int64, error) {
data, err := io.ReadAll(r)
if err != nil {
@@ -508,7 +478,7 @@ func (s *VariantStorage) Store(
}
// Load returns a reader for the content at the given key.
func (s *VariantStorage) Load(key VariantKey) (io.ReadCloser, error) {
func (s *VariantStorage) Load(key imagerequest.VariantKey) (io.ReadCloser, error) {
path := s.keyToPath(key)
f, err := os.Open(path) //nolint:gosec // path derived from cache key
@@ -525,7 +495,9 @@ func (s *VariantStorage) Load(key VariantKey) (io.ReadCloser, error) {
// LoadWithSize returns a reader and file size for the content at the
// given key.
func (s *VariantStorage) LoadWithSize(key VariantKey) (io.ReadCloser, int64, error) {
func (s *VariantStorage) LoadWithSize(
key imagerequest.VariantKey,
) (io.ReadCloser, int64, error) {
path := s.keyToPath(key)
f, err := os.Open(path) //nolint:gosec // path derived from cache key
@@ -551,7 +523,7 @@ func (s *VariantStorage) LoadWithSize(key VariantKey) (io.ReadCloser, int64, err
// the given key. The content type is read from the .meta file, and is
// empty when that file is missing or unreadable.
func (s *VariantStorage) LoadWithMeta(
key VariantKey,
key imagerequest.VariantKey,
) (io.ReadCloser, int64, string, error) {
f, size, err := s.LoadWithSize(key)
if err != nil {
@@ -574,7 +546,7 @@ func (s *VariantStorage) LoadWithMeta(
}
// Exists checks if content exists at the given key.
func (s *VariantStorage) Exists(key VariantKey) bool {
func (s *VariantStorage) Exists(key imagerequest.VariantKey) bool {
path := s.keyToPath(key)
_, err := os.Stat(path)
@@ -583,7 +555,7 @@ func (s *VariantStorage) Exists(key VariantKey) bool {
// Delete removes the content at the given key together with its .meta
// sidecar file. A missing file is not an error.
func (s *VariantStorage) Delete(key VariantKey) error {
func (s *VariantStorage) Delete(key imagerequest.VariantKey) error {
path := s.keyToPath(key)
err := os.Remove(path)
@@ -602,7 +574,7 @@ func (s *VariantStorage) Delete(key VariantKey) error {
}
// keyToPath converts a key to a file path: <basedir>/<ab>/<cd>/<key>
func (s *VariantStorage) keyToPath(key VariantKey) string {
func (s *VariantStorage) keyToPath(key imagerequest.VariantKey) string {
k := string(key)
if len(k) < MinHashLength {
return filepath.Join(s.baseDir, k)