Files
imgproxy/fetcher/errors.go
2025-09-11 18:23:29 +02:00

196 lines
4.7 KiB
Go

package fetcher
import (
"context"
"errors"
"fmt"
"io"
"net/http"
"github.com/imgproxy/imgproxy/v3/fetcher/transport/generichttp"
"github.com/imgproxy/imgproxy/v3/ierrors"
)
const msgSourceImageIsUnreachable = "Source image is unreachable"
type (
ImageRequestError struct{ error }
ImageRequstSchemeError string
ImagePartialResponseError string
ImageResponseStatusError string
ImageTooManyRedirectsError string
ImageRequestCanceledError struct{ error }
ImageRequestTimeoutError struct{ error }
NotModifiedError struct {
headers http.Header
}
httpError interface {
Timeout() bool
}
)
func newImageRequestError(err error) error {
return ierrors.Wrap(
ImageRequestError{err},
1,
ierrors.WithStatusCode(http.StatusNotFound),
ierrors.WithPublicMessage(msgSourceImageIsUnreachable),
ierrors.WithShouldReport(false),
)
}
func (e ImageRequestError) Unwrap() error {
return e.error
}
func newImageRequstSchemeError(scheme string) error {
return ierrors.Wrap(
ImageRequstSchemeError(fmt.Sprintf("Unknown scheme: %s", scheme)),
1,
ierrors.WithStatusCode(http.StatusNotFound),
ierrors.WithPublicMessage(msgSourceImageIsUnreachable),
ierrors.WithShouldReport(false),
)
}
func (e ImageRequstSchemeError) Error() string { return string(e) }
func newImagePartialResponseError(msg string) error {
return ierrors.Wrap(
ImagePartialResponseError(msg),
1,
ierrors.WithStatusCode(http.StatusNotFound),
ierrors.WithPublicMessage(msgSourceImageIsUnreachable),
ierrors.WithShouldReport(false),
)
}
func (e ImagePartialResponseError) Error() string { return string(e) }
func newImageResponseStatusError(status int, body string) error {
var msg string
if len(body) > 0 {
msg = fmt.Sprintf("Status: %d; %s", status, body)
} else {
msg = fmt.Sprintf("Status: %d", status)
}
statusCode := 404
if status >= 500 {
statusCode = 500
}
return ierrors.Wrap(
ImageResponseStatusError(msg),
1,
ierrors.WithStatusCode(statusCode),
ierrors.WithPublicMessage(msgSourceImageIsUnreachable),
ierrors.WithShouldReport(false),
)
}
func (e ImageResponseStatusError) Error() string { return string(e) }
func newImageTooManyRedirectsError(n int) error {
return ierrors.Wrap(
ImageTooManyRedirectsError(fmt.Sprintf("Stopped after %d redirects", n)),
1,
ierrors.WithStatusCode(http.StatusNotFound),
ierrors.WithPublicMessage(msgSourceImageIsUnreachable),
ierrors.WithShouldReport(false),
)
}
func (e ImageTooManyRedirectsError) Error() string { return string(e) }
func newImageRequestCanceledError(err error) error {
return ierrors.Wrap(
ImageRequestCanceledError{err},
2,
ierrors.WithStatusCode(499),
ierrors.WithPublicMessage(msgSourceImageIsUnreachable),
ierrors.WithShouldReport(false),
)
}
func (e ImageRequestCanceledError) Error() string {
return fmt.Sprintf("The image request is cancelled: %s", e.error)
}
func (e ImageRequestCanceledError) Unwrap() error { return e.error }
func newImageRequestTimeoutError(err error) error {
return ierrors.Wrap(
ImageRequestTimeoutError{err},
2,
ierrors.WithStatusCode(http.StatusGatewayTimeout),
ierrors.WithPublicMessage(msgSourceImageIsUnreachable),
ierrors.WithShouldReport(false),
)
}
func (e ImageRequestTimeoutError) Error() string {
return fmt.Sprintf("The image request timed out: %s", e.error)
}
func (e ImageRequestTimeoutError) Unwrap() error { return e.error }
func newNotModifiedError(headers http.Header) error {
return ierrors.Wrap(
NotModifiedError{headers},
1,
ierrors.WithStatusCode(http.StatusNotModified),
ierrors.WithPublicMessage("Not modified"),
ierrors.WithShouldReport(false),
)
}
func (e NotModifiedError) Error() string { return "Not modified" }
func (e NotModifiedError) Headers() http.Header {
return e.headers
}
// NOTE: make private when we remove download functions from imagedata package
func WrapError(err error) error {
isTimeout := false
var secArrdErr generichttp.SourceAddressError
switch {
case errors.Is(err, context.DeadlineExceeded):
isTimeout = true
case errors.Is(err, context.Canceled):
return newImageRequestCanceledError(err)
case err == io.ErrUnexpectedEOF:
return ierrors.Wrap(
newImageRequestError(err),
1,
ierrors.WithPublicMessage("source image is corrupted"),
ierrors.WithShouldReport(false),
ierrors.WithStatusCode(http.StatusUnprocessableEntity),
)
case errors.As(err, &secArrdErr):
return ierrors.Wrap(
err,
1,
ierrors.WithStatusCode(404),
ierrors.WithPublicMessage(msgSourceImageIsUnreachable),
ierrors.WithShouldReport(false),
)
default:
if httpErr, ok := err.(httpError); ok {
isTimeout = httpErr.Timeout()
}
}
if isTimeout {
return newImageRequestTimeoutError(err)
}
return ierrors.Wrap(err, 1)
}