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
7 changed files with 50 additions and 50 deletions

No files matched your search

@@ -86,7 +86,7 @@ func checkProcessStopsAtDeadline(
proc := New(Params{MaxConcurrentProcessing: 1}) proc := New(Params{MaxConcurrentProcessing: 1})
result, err := proc.Process(ctx, input, smallJPEGTransform()) result, err := proc.Process(ctx, input, smallJPEGRequest())
if result != nil { if result != nil {
t.Logf("Process() made a %dx%d image after its deadline passed", t.Logf("Process() made a %dx%d image after its deadline passed",
result.Width, result.Height) result.Width, result.Height)
@@ -14,11 +14,11 @@ import (
// processedSize runs input through Process and returns the output's size in // processedSize runs input through Process and returns the output's size in
// bytes. // bytes.
func processedSize( func processedSize(
t *testing.T, input []byte, transform *imagerequest.Transform, t *testing.T, input []byte, req *imagerequest.Transform,
) int64 { ) int64 {
t.Helper() t.Helper()
result, err := New(Params{}).Process(t.Context(), bytes.NewReader(input), transform) result, err := New(Params{}).Process(t.Context(), bytes.NewReader(input), req)
if err != nil { if err != nil {
t.Fatalf("Process() error = %v", err) t.Fatalf("Process() error = %v", err)
} }
@@ -124,14 +124,14 @@ func TestImageProcessor_ResizeJPEG(t *testing.T) {
input := createTestJPEG(t, 800, 600) input := createTestJPEG(t, 800, 600)
transform := &imagerequest.Transform{ req := &imagerequest.Transform{
Size: imagerequest.Size{Width: 400, Height: 300}, Size: imagerequest.Size{Width: 400, Height: 300},
Format: imagerequest.FormatJPEG, Format: imagerequest.FormatJPEG,
Quality: 85, Quality: 85,
FitMode: imagerequest.FitCover, FitMode: imagerequest.FitCover,
} }
result, err := proc.Process(ctx, bytes.NewReader(input), transform) result, err := proc.Process(ctx, bytes.NewReader(input), req)
if err != nil { if err != nil {
t.Fatalf("Process() error = %v", err) t.Fatalf("Process() error = %v", err)
} }
@@ -170,13 +170,13 @@ func TestImageProcessor_ConvertToPNG(t *testing.T) {
input := createTestJPEG(t, 200, 150) input := createTestJPEG(t, 200, 150)
transform := &imagerequest.Transform{ req := &imagerequest.Transform{
Size: imagerequest.Size{Width: 200, Height: 150}, Size: imagerequest.Size{Width: 200, Height: 150},
Format: imagerequest.FormatPNG, Format: imagerequest.FormatPNG,
FitMode: imagerequest.FitCover, FitMode: imagerequest.FitCover,
} }
result, err := proc.Process(ctx, bytes.NewReader(input), transform) result, err := proc.Process(ctx, bytes.NewReader(input), req)
if err != nil { if err != nil {
t.Fatalf("Process() error = %v", err) t.Fatalf("Process() error = %v", err)
} }
@@ -206,14 +206,14 @@ func processAndCheckSize(
input := createTestJPEG(t, inputW, inputH) input := createTestJPEG(t, inputW, inputH)
transform := &imagerequest.Transform{ req := &imagerequest.Transform{
Size: size, Size: size,
Format: imagerequest.FormatJPEG, Format: imagerequest.FormatJPEG,
Quality: 85, Quality: 85,
FitMode: imagerequest.FitCover, FitMode: imagerequest.FitCover,
} }
result, err := proc.Process(ctx, bytes.NewReader(input), transform) result, err := proc.Process(ctx, bytes.NewReader(input), req)
if err != nil { if err != nil {
t.Fatalf("Process() error = %v", err) t.Fatalf("Process() error = %v", err)
} }
@@ -246,14 +246,14 @@ func TestImageProcessor_FitContain(t *testing.T) {
// Should result in 400x200 (maintaining aspect ratio) // Should result in 400x200 (maintaining aspect ratio)
input := createTestJPEG(t, 800, 400) input := createTestJPEG(t, 800, 400)
transform := &imagerequest.Transform{ req := &imagerequest.Transform{
Size: imagerequest.Size{Width: 400, Height: 400}, Size: imagerequest.Size{Width: 400, Height: 400},
Format: imagerequest.FormatJPEG, Format: imagerequest.FormatJPEG,
Quality: 85, Quality: 85,
FitMode: imagerequest.FitContain, FitMode: imagerequest.FitContain,
} }
result, err := proc.Process(ctx, bytes.NewReader(input), transform) result, err := proc.Process(ctx, bytes.NewReader(input), req)
if err != nil { if err != nil {
t.Fatalf("Process() error = %v", err) t.Fatalf("Process() error = %v", err)
} }
@@ -290,13 +290,13 @@ func TestImageProcessor_ProcessPNG(t *testing.T) {
input := createTestPNG(t, 400, 300) input := createTestPNG(t, 400, 300)
transform := &imagerequest.Transform{ req := &imagerequest.Transform{
Size: imagerequest.Size{Width: 200, Height: 150}, Size: imagerequest.Size{Width: 200, Height: 150},
Format: imagerequest.FormatPNG, Format: imagerequest.FormatPNG,
FitMode: imagerequest.FitCover, FitMode: imagerequest.FitCover,
} }
result, err := proc.Process(ctx, bytes.NewReader(input), transform) result, err := proc.Process(ctx, bytes.NewReader(input), req)
if err != nil { if err != nil {
t.Fatalf("Process() error = %v", err) t.Fatalf("Process() error = %v", err)
} }
@@ -350,14 +350,14 @@ func TestImageProcessor_RejectsOversizedInput(t *testing.T) {
ctx := context.Background() ctx := context.Background()
input := createTestJPEG(t, tt.width, tt.height) input := createTestJPEG(t, tt.width, tt.height)
transform := &imagerequest.Transform{ req := &imagerequest.Transform{
Size: imagerequest.Size{Width: 100, Height: 100}, Size: imagerequest.Size{Width: 100, Height: 100},
Format: imagerequest.FormatJPEG, Format: imagerequest.FormatJPEG,
Quality: 85, Quality: 85,
FitMode: imagerequest.FitCover, FitMode: imagerequest.FitCover,
} }
_, err := proc.Process(ctx, bytes.NewReader(input), transform) _, err := proc.Process(ctx, bytes.NewReader(input), req)
if err == nil { if err == nil {
t.Error("Process() should reject oversized input images") t.Error("Process() should reject oversized input images")
} }
@@ -378,14 +378,14 @@ func TestImageProcessor_AcceptsMaxDimensionInput(t *testing.T) {
// Create an image at exactly MaxInputDimension - should be accepted // Create an image at exactly MaxInputDimension - should be accepted
input := createTestJPEG(t, MaxInputDimension, 100) input := createTestJPEG(t, MaxInputDimension, 100)
transform := &imagerequest.Transform{ req := &imagerequest.Transform{
Size: imagerequest.Size{Width: 100, Height: 100}, Size: imagerequest.Size{Width: 100, Height: 100},
Format: imagerequest.FormatJPEG, Format: imagerequest.FormatJPEG,
Quality: 85, Quality: 85,
FitMode: imagerequest.FitCover, FitMode: imagerequest.FitCover,
} }
result, err := proc.Process(ctx, bytes.NewReader(input), transform) result, err := proc.Process(ctx, bytes.NewReader(input), req)
if err != nil { if err != nil {
t.Fatalf( t.Fatalf(
"Process() should accept images at MaxInputDimension, got error: %v", "Process() should accept images at MaxInputDimension, got error: %v",
@@ -408,14 +408,14 @@ func encodeAndCheck(
input := createTestJPEG(t, 200, 150) input := createTestJPEG(t, 200, 150)
transform := &imagerequest.Transform{ req := &imagerequest.Transform{
Size: imagerequest.Size{Width: 100, Height: 75}, Size: imagerequest.Size{Width: 100, Height: 75},
Format: format, Format: format,
Quality: quality, Quality: quality,
FitMode: imagerequest.FitCover, FitMode: imagerequest.FitCover,
} }
result, err := proc.Process(ctx, bytes.NewReader(input), transform) result, err := proc.Process(ctx, bytes.NewReader(input), req)
if err != nil { if err != nil {
t.Fatalf("Process() error = %v, want nil", err) t.Fatalf("Process() error = %v, want nil", err)
} }
@@ -462,14 +462,14 @@ func TestImageProcessor_DecodeAVIF(t *testing.T) {
} }
// Request resize and convert to JPEG // Request resize and convert to JPEG
transform := &imagerequest.Transform{ req := &imagerequest.Transform{
Size: imagerequest.Size{Width: 2, Height: 2}, Size: imagerequest.Size{Width: 2, Height: 2},
Format: imagerequest.FormatJPEG, Format: imagerequest.FormatJPEG,
Quality: 85, Quality: 85,
FitMode: imagerequest.FitCover, FitMode: imagerequest.FitCover,
} }
result, err := proc.Process(ctx, bytes.NewReader(input), transform) result, err := proc.Process(ctx, bytes.NewReader(input), req)
if err != nil { if err != nil {
t.Fatalf("Process() error = %v, want nil (AVIF decoding should work)", err) t.Fatalf("Process() error = %v, want nil (AVIF decoding should work)", err)
} }
@@ -503,14 +503,14 @@ func TestImageProcessor_RejectsOversizedInputData(t *testing.T) {
t.Fatalf("test JPEG must exceed %d bytes, got %d", limit, len(input)) t.Fatalf("test JPEG must exceed %d bytes, got %d", limit, len(input))
} }
transform := &imagerequest.Transform{ req := &imagerequest.Transform{
Size: imagerequest.Size{Width: 100, Height: 75}, Size: imagerequest.Size{Width: 100, Height: 75},
Format: imagerequest.FormatJPEG, Format: imagerequest.FormatJPEG,
Quality: 85, Quality: 85,
FitMode: imagerequest.FitCover, FitMode: imagerequest.FitCover,
} }
_, err := proc.Process(ctx, bytes.NewReader(input), transform) _, err := proc.Process(ctx, bytes.NewReader(input), req)
if err == nil { if err == nil {
t.Fatal("Process() should reject input exceeding maxInputBytes") t.Fatal("Process() should reject input exceeding maxInputBytes")
} }
@@ -530,14 +530,14 @@ func TestImageProcessor_AcceptsInputWithinLimit(t *testing.T) {
proc := New(Params{MaxInputBytes: limit}) proc := New(Params{MaxInputBytes: limit})
ctx := context.Background() ctx := context.Background()
transform := &imagerequest.Transform{ req := &imagerequest.Transform{
Size: imagerequest.Size{Width: 10, Height: 10}, Size: imagerequest.Size{Width: 10, Height: 10},
Format: imagerequest.FormatJPEG, Format: imagerequest.FormatJPEG,
Quality: 85, Quality: 85,
FitMode: imagerequest.FitCover, FitMode: imagerequest.FitCover,
} }
result, err := proc.Process(ctx, bytes.NewReader(input), transform) result, err := proc.Process(ctx, bytes.NewReader(input), req)
if err != nil { if err != nil {
t.Fatalf("Process() error = %v, want nil", err) t.Fatalf("Process() error = %v, want nil", err)
} }
@@ -570,12 +570,12 @@ func TestImageProcessor_EncodeAVIF(t *testing.T) {
// processAndDecode runs input through Process and decodes the output with // processAndDecode runs input through Process and decodes the output with
// vips, so a test can inspect the image a client would receive. // vips, so a test can inspect the image a client would receive.
func processAndDecode( func processAndDecode(
t *testing.T, input []byte, transform *imagerequest.Transform, t *testing.T, input []byte, req *imagerequest.Transform,
) *vips.ImageRef { ) *vips.ImageRef {
t.Helper() t.Helper()
result, err := New(Params{}).Process( result, err := New(Params{}).Process(
context.Background(), bytes.NewReader(input), transform, context.Background(), bytes.NewReader(input), req,
) )
if err != nil { if err != nil {
t.Fatalf("Process() error = %v", err) t.Fatalf("Process() error = %v", err)
@@ -16,13 +16,13 @@ import (
func TestImageProcessor_EncodeJPEGXL(t *testing.T) { func TestImageProcessor_EncodeJPEGXL(t *testing.T) {
t.Parallel() t.Parallel()
transform := &imagerequest.Transform{ req := &imagerequest.Transform{
Size: imagerequest.Size{Width: 100, Height: 75}, Size: imagerequest.Size{Width: 100, Height: 75},
Format: imagerequest.FormatJXL, Format: imagerequest.FormatJXL,
} }
result, err := New(Params{}).Process( result, err := New(Params{}).Process(
t.Context(), bytes.NewReader(createTestJPEG(t, 200, 150)), transform, t.Context(), bytes.NewReader(createTestJPEG(t, 200, 150)), req,
) )
if err != nil { if err != nil {
t.Fatalf("Process() error = %v", err) t.Fatalf("Process() error = %v", err)
@@ -78,8 +78,8 @@ func (r *gatedReader) Read(p []byte) (int, error) {
return n, err return n, err
} }
// smallJPEGTransform asks for a 5x5 JPEG. // smallJPEGRequest asks for a 5x5 JPEG.
func smallJPEGTransform() *imagerequest.Transform { func smallJPEGRequest() *imagerequest.Transform {
return &imagerequest.Transform{ return &imagerequest.Transform{
Size: imagerequest.Size{Width: 5, Height: 5}, Size: imagerequest.Size{Width: 5, Height: 5},
Format: imagerequest.FormatJPEG, Format: imagerequest.FormatJPEG,
@@ -94,7 +94,7 @@ func processInBackground(
proc *ImageProcessor, reader *gatedReader, results chan<- error, proc *ImageProcessor, reader *gatedReader, results chan<- error,
) { ) {
go func() { go func() {
result, err := proc.Process(context.Background(), reader, smallJPEGTransform()) result, err := proc.Process(context.Background(), reader, smallJPEGRequest())
if err == nil { if err == nil {
_ = result.Content.Close() _ = result.Content.Close()
} }
@@ -239,7 +239,7 @@ func TestProcessWaitsThenFailsWhenNoSlotFrees(t *testing.T) {
start := time.Now() start := time.Now()
_, err := proc.Process(context.Background(), bytes.NewReader(input), _, err := proc.Process(context.Background(), bytes.NewReader(input),
smallJPEGTransform()) smallJPEGRequest())
if !errors.Is(err, ErrTooManyImages) { if !errors.Is(err, ErrTooManyImages) {
t.Fatalf("Process() error = %v, want ErrTooManyImages", err) t.Fatalf("Process() error = %v, want ErrTooManyImages", err)
} }
@@ -265,37 +265,37 @@ func TestProcessReleasesSlotOnError(t *testing.T) {
valid := createTestJPEG(t, 10, 10) valid := createTestJPEG(t, 10, 10)
unsupported := smallJPEGTransform() unsupported := smallJPEGRequest()
unsupported.Format = "bmp" unsupported.Format = "bmp"
cases := []struct { cases := []struct {
name string name string
input io.Reader input io.Reader
transform *imagerequest.Transform req *imagerequest.Transform
// want is the error Process must return; nil means any error. // want is the error Process must return; nil means any error.
want error want error
}{ }{
{ {
name: "input cannot be read", name: "input cannot be read",
input: iotest.ErrReader(errTestReadFailed), input: iotest.ErrReader(errTestReadFailed),
transform: smallJPEGTransform(), req: smallJPEGRequest(),
want: errTestReadFailed, want: errTestReadFailed,
}, },
{ {
name: "input over the byte limit", name: "input over the byte limit",
input: bytes.NewReader(createTestJPEG(t, 800, 600)), input: bytes.NewReader(createTestJPEG(t, 800, 600)),
transform: smallJPEGTransform(), req: smallJPEGRequest(),
want: ErrInputDataTooLarge, want: ErrInputDataTooLarge,
}, },
{ {
name: "input not an image", name: "input not an image",
input: strings.NewReader("not an image"), input: strings.NewReader("not an image"),
transform: smallJPEGTransform(), req: smallJPEGRequest(),
}, },
{ {
name: "output format not supported", name: "output format not supported",
input: bytes.NewReader(valid), input: bytes.NewReader(valid),
transform: unsupported, req: unsupported,
want: ErrUnsupportedOutputFormat, want: ErrUnsupportedOutputFormat,
}, },
} }
@@ -307,7 +307,7 @@ func TestProcessReleasesSlotOnError(t *testing.T) {
proc := New(Params{MaxInputBytes: 4096, MaxConcurrentProcessing: 1}) proc := New(Params{MaxInputBytes: 4096, MaxConcurrentProcessing: 1})
proc.processingWaitTimeout = 100 * time.Millisecond proc.processingWaitTimeout = 100 * time.Millisecond
_, err := proc.Process(context.Background(), tc.input, tc.transform) _, err := proc.Process(context.Background(), tc.input, tc.req)
if err == nil || (tc.want != nil && !errors.Is(err, tc.want)) { if err == nil || (tc.want != nil && !errors.Is(err, tc.want)) {
t.Fatalf("Process() error = %v, want %v", err, tc.want) t.Fatalf("Process() error = %v, want %v", err, tc.want)
} }
@@ -317,7 +317,7 @@ func TestProcessReleasesSlotOnError(t *testing.T) {
} }
result, err := proc.Process(context.Background(), bytes.NewReader(valid), result, err := proc.Process(context.Background(), bytes.NewReader(valid),
smallJPEGTransform()) smallJPEGRequest())
if err != nil { if err != nil {
t.Fatalf("Process() after the error = %v, want nil", err) t.Fatalf("Process() after the error = %v, want nil", err)
} }
@@ -2,7 +2,7 @@ package imagerequest
import "testing" import "testing"
func TestRequest_SourceURL_DefaultHTTPS(t *testing.T) { func TestImageRequest_SourceURL_DefaultHTTPS(t *testing.T) {
t.Parallel() t.Parallel()
req := &Request{ req := &Request{
@@ -21,7 +21,7 @@ func TestRequest_SourceURL_DefaultHTTPS(t *testing.T) {
} }
} }
func TestRequest_SourceURL_AllowHTTP(t *testing.T) { func TestImageRequest_SourceURL_AllowHTTP(t *testing.T) {
t.Parallel() t.Parallel()
req := &Request{ req := &Request{
@@ -40,7 +40,7 @@ func TestRequest_SourceURL_AllowHTTP(t *testing.T) {
} }
} }
func TestRequest_SourceURL_AllowHTTPFalse(t *testing.T) { func TestImageRequest_SourceURL_AllowHTTPFalse(t *testing.T) {
t.Parallel() t.Parallel()
req := &Request{ req := &Request{
@@ -12,9 +12,9 @@ const (
testPathCat = "/photos/cat.jpg" testPathCat = "/photos/cat.jpg"
) )
// assertRequest compares the source host, path and query and the size and // assertParsedURL compares all fields of a parsed URL against the
// format of got against want. // expected value.
func assertRequest(t *testing.T, got, want *Request) { func assertParsedURL(t *testing.T, got, want *Request) {
t.Helper() t.Helper()
if got.Source.Host != want.Source.Host { if got.Source.Host != want.Source.Host {
@@ -116,7 +116,7 @@ func TestParseImageURL(t *testing.T) {
t.Fatalf("ParseImageURL() unexpected error = %v", err) t.Fatalf("ParseImageURL() unexpected error = %v", err)
} }
assertRequest(t, got, tt.want) assertParsedURL(t, got, tt.want)
}) })
} }
} }
@@ -247,7 +247,7 @@ func TestParseImagePath(t *testing.T) {
return return
} }
assertRequest(t, got, tt.want) assertParsedURL(t, got, tt.want)
}) })
} }
} }
@@ -432,7 +432,7 @@ func TestParseImageURL_SourceURLHoldingPercent25(t *testing.T) {
t.Fatalf("ParseImageURL() unexpected error = %v", err) t.Fatalf("ParseImageURL() unexpected error = %v", err)
} }
assertRequest(t, got, tt.want) assertParsedURL(t, got, tt.want)
}) })
} }
} }