1 Commits
Author SHA1 Message Date
sneak 0b1113878e 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 CBOR field names, so tokens made before still decode. VariantKey
moves with the function that makes it. Tests follow the moved code; the
tests of the removed conversions go with them.

Model: opus-5-5
2026-10-21 12:09:05 +00:00
3 changed files with 16 additions and 14 deletions

No files matched your search

+8 -8
View File
@@ -145,7 +145,7 @@ func (s *Handlers) HandleGenerateURL() http.HandlerFunc {
return return
} }
payload, expiresAt, ttl, err := buildGeneratePayload(parsed, r.Form) req, expiresAt, ttl, err := buildGenerateRequest(parsed, r.Form)
if err != nil { if err != nil {
s.renderGeneratorWithForm(w, r, err.Error(), r.Form, s.renderGeneratorWithForm(w, r, err.Error(), r.Form,
http.StatusBadRequest) http.StatusBadRequest)
@@ -154,8 +154,8 @@ func (s *Handlers) HandleGenerateURL() http.HandlerFunc {
} }
// The image URL must fetch the source URL as it was written // The image URL must fetch the source URL as it was written
payload.AllowHTTP = s.config.AllowHTTP req.AllowHTTP = s.config.AllowHTTP
fetchURL := payload.SourceURL() fetchURL := req.SourceURL()
if !fetchedAsWritten(parsed, fetchURL) { if !fetchedAsWritten(parsed, fetchURL) {
s.renderGeneratorWithForm(w, r, "Source URL cannot be fetched as "+ s.renderGeneratorWithForm(w, r, "Source URL cannot be fetched as "+
@@ -166,7 +166,7 @@ func (s *Handlers) HandleGenerateURL() http.HandlerFunc {
} }
// Generate encrypted token // Generate encrypted token
token, err := s.encGen.Generate(payload) token, err := s.encGen.Generate(req)
if err != nil { if err != nil {
s.log.Error("failed to generate encrypted URL", "error", err) s.log.Error("failed to generate encrypted URL", "error", err)
s.renderGeneratorWithForm(w, r, "Failed to generate URL", r.Form, s.renderGeneratorWithForm(w, r, "Failed to generate URL", r.Form,
@@ -239,13 +239,13 @@ func fetchedAsWritten(source *url.URL, fetchURL string) bool {
fetched.RawQuery == source.RawQuery fetched.RawQuery == source.RawQuery
} }
// buildGeneratePayload parses the numeric form fields and assembles the // buildGenerateRequest parses the numeric form fields and assembles the
// image request the encrypted URL's token is made from. ttl=0 means never // image request the encrypted URL's token is made from. ttl=0 means never
// expires (Expires stays the zero time). A non-numeric or out-of-range // expires (Expires stays the zero time). A non-numeric or out-of-range
// width, height, quality or ttl, or an unrecognized fit mode, is a client // width, height, quality or ttl, or an unrecognized fit mode, is a client
// error naming the offending field. The format field is passed through // error naming the offending field. The format field is passed through
// unchecked. // unchecked.
func buildGeneratePayload( func buildGenerateRequest(
parsed *url.URL, form url.Values, parsed *url.URL, form url.Values,
) (*imagerequest.Request, time.Time, int, error) { ) (*imagerequest.Request, time.Time, int, error) {
width, err := parseFormDimension(form, "width") width, err := parseFormDimension(form, "width")
@@ -282,7 +282,7 @@ func buildGeneratePayload(
expiresAt = time.Now().Add(time.Duration(ttl) * time.Second) expiresAt = time.Now().Add(time.Duration(ttl) * time.Second)
} }
payload := &imagerequest.Request{ req := &imagerequest.Request{
Source: imagerequest.Source{ Source: imagerequest.Source{
Host: parsed.Host, Host: parsed.Host,
Path: parsed.Path, Path: parsed.Path,
@@ -297,7 +297,7 @@ func buildGeneratePayload(
Expires: expiresAt, Expires: expiresAt,
} }
return payload, expiresAt, ttl, nil return req, expiresAt, ttl, nil
} }
// parseFormDimension reads an optional width or height form field. An empty // parseFormDimension reads an optional width or height form field. An empty
@@ -125,10 +125,10 @@ func TestGeneratePostRejectsBadField(t *testing.T) {
} }
} }
// TestBuildGeneratePayloadDefaultAndLimits verifies that an empty quality // TestBuildGenerateRequestDefaultAndLimits verifies that an empty quality
// takes the default, and that the largest accepted width, height, quality and // takes the default, and that the largest accepted width, height, quality and
// ttl are accepted with an expiry still in the future. // ttl are accepted with an expiry still in the future.
func TestBuildGeneratePayloadDefaultAndLimits(t *testing.T) { func TestBuildGenerateRequestDefaultAndLimits(t *testing.T) {
t.Parallel() t.Parallel()
parsed, err := url.Parse(testSourceURL) parsed, err := url.Parse(testSourceURL)
@@ -136,17 +136,17 @@ func TestBuildGeneratePayloadDefaultAndLimits(t *testing.T) {
t.Fatalf("url.Parse() error = %v", err) t.Fatalf("url.Parse() error = %v", err)
} }
payload, _, _, err := buildGeneratePayload(parsed, url.Values{}) req, _, _, err := buildGenerateRequest(parsed, url.Values{})
if err != nil { if err != nil {
t.Fatalf("empty form: error = %v", err) t.Fatalf("empty form: error = %v", err)
} }
if payload.Transform.Quality != encurl.DefaultQuality { if req.Transform.Quality != encurl.DefaultQuality {
t.Errorf("empty quality gave %d, want %d", t.Errorf("empty quality gave %d, want %d",
payload.Transform.Quality, encurl.DefaultQuality) req.Transform.Quality, encurl.DefaultQuality)
} }
_, expiresAt, _, err := buildGeneratePayload(parsed, url.Values{ _, expiresAt, _, err := buildGenerateRequest(parsed, url.Values{
widthField: {strconv.Itoa(imagerequest.MaxDimension)}, widthField: {strconv.Itoa(imagerequest.MaxDimension)},
heightField: {strconv.Itoa(imagerequest.MaxDimension)}, heightField: {strconv.Itoa(imagerequest.MaxDimension)},
qualityField: {strconv.Itoa(maxQuality)}, qualityField: {strconv.Itoa(maxQuality)},
@@ -381,6 +381,7 @@ func TestService_Get_InvalidFile(t *testing.T) {
req := photoVariant(fixtures, 85, imagerequest.FitCover) req := photoVariant(fixtures, 85, imagerequest.FitCover)
req.Source.Path = "/images/fake.jpg" req.Source.Path = "/images/fake.jpg"
req.Transform.Size = imagerequest.Size{Width: 50, Height: 50}
// Should fail because magic bytes don't match // Should fail because magic bytes don't match
_, err := svc.Get(ctx, req) _, err := svc.Get(ctx, req)
@@ -397,6 +398,7 @@ func TestService_Get_NotFound(t *testing.T) {
req := photoVariant(fixtures, 85, imagerequest.FitCover) req := photoVariant(fixtures, 85, imagerequest.FitCover)
req.Source.Path = "/images/nonexistent.jpg" req.Source.Path = "/images/nonexistent.jpg"
req.Transform.Size = imagerequest.Size{Width: 50, Height: 50}
_, err := svc.Get(ctx, req) _, err := svc.Get(ctx, req)
if err == nil { if err == nil {