mirror of
https://github.com/imgproxy/imgproxy.git
synced 2025-09-29 04:53:05 +02:00
Clone with testutil.Helper
This commit is contained in:
@@ -27,7 +27,7 @@ type Config struct {
|
||||
Handlers HandlerConfigs
|
||||
Server server.Config
|
||||
Security security.Config
|
||||
ProcessingOptions options.Config
|
||||
Options options.Config
|
||||
}
|
||||
|
||||
// NewDefaultConfig creates a new default configuration
|
||||
@@ -43,7 +43,7 @@ func NewDefaultConfig() Config {
|
||||
},
|
||||
Server: server.NewDefaultConfig(),
|
||||
Security: security.NewDefaultConfig(),
|
||||
ProcessingOptions: options.NewDefaultConfig(),
|
||||
Options: options.NewDefaultConfig(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ func LoadConfigFromEnv(c *Config) (*Config, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err = options.LoadConfigFromEnv(&c.ProcessingOptions); err != nil {
|
||||
if _, err = options.LoadConfigFromEnv(&c.Options); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
7
go.mod
7
go.mod
@@ -11,6 +11,7 @@ require (
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.1
|
||||
github.com/DarthSim/godotenv v1.3.1
|
||||
github.com/DataDog/datadog-agent/pkg/trace v0.67.0
|
||||
github.com/DataDog/datadog-go/v5 v5.6.0
|
||||
github.com/DataDog/dd-trace-go/v2 v2.0.1
|
||||
github.com/airbrake/gobrake/v5 v5.6.2
|
||||
@@ -42,6 +43,7 @@ require (
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/tdewolff/parse/v2 v2.8.1
|
||||
github.com/trimmer-io/go-xmp v1.0.0
|
||||
github.com/urfave/cli/v3 v3.4.1
|
||||
go.opentelemetry.io/contrib/detectors/aws/ec2 v1.37.0
|
||||
go.opentelemetry.io/contrib/detectors/aws/ecs v1.37.0
|
||||
go.opentelemetry.io/contrib/detectors/aws/eks v1.37.0
|
||||
@@ -58,7 +60,6 @@ require (
|
||||
go.opentelemetry.io/otel/sdk/metric v1.37.0
|
||||
go.opentelemetry.io/otel/trace v1.37.0
|
||||
go.uber.org/automaxprocs v1.6.0
|
||||
golang.org/x/image v0.28.0
|
||||
golang.org/x/net v0.41.0
|
||||
golang.org/x/sync v0.15.0
|
||||
golang.org/x/sys v0.33.0
|
||||
@@ -82,7 +83,6 @@ require (
|
||||
github.com/DataDog/datadog-agent/pkg/obfuscate v0.67.0 // indirect
|
||||
github.com/DataDog/datadog-agent/pkg/proto v0.67.0 // indirect
|
||||
github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.67.0 // indirect
|
||||
github.com/DataDog/datadog-agent/pkg/trace v0.67.0 // indirect
|
||||
github.com/DataDog/datadog-agent/pkg/util/log v0.67.0 // indirect
|
||||
github.com/DataDog/datadog-agent/pkg/util/scrubber v0.67.0 // indirect
|
||||
github.com/DataDog/datadog-agent/pkg/version v0.67.0 // indirect
|
||||
@@ -119,7 +119,6 @@ require (
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/cihub/seelog v0.0.0-20170130134532-f561c5e57575 // indirect
|
||||
github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/eapache/queue/v2 v2.0.0-20230407133247-75960ed334e4 // indirect
|
||||
@@ -175,7 +174,6 @@ require (
|
||||
github.com/prometheus/common v0.65.0 // indirect
|
||||
github.com/prometheus/procfs v0.17.0 // indirect
|
||||
github.com/puzpuzpuz/xsync/v3 v3.5.1 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 // indirect
|
||||
github.com/secure-systems-lab/go-securesystemslib v0.9.0 // indirect
|
||||
github.com/shabbyrobe/gocovmerge v0.0.0-20190829150210-3e036491d500 // indirect
|
||||
@@ -184,7 +182,6 @@ require (
|
||||
github.com/tinylib/msgp v1.3.0 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.15 // indirect
|
||||
github.com/tklauser/numcpus v0.10.0 // indirect
|
||||
github.com/urfave/cli/v3 v3.4.1 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
github.com/zeebo/errs v1.4.0 // indirect
|
||||
|
9
go.sum
9
go.sum
@@ -44,7 +44,6 @@ github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mo
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
github.com/DarthSim/godotenv v1.3.1 h1:NMWdswlRx2M9uPY4Ux8p/Q/rDs7A97OG89fECiQ/Tz0=
|
||||
github.com/DarthSim/godotenv v1.3.1/go.mod h1:B3ySe1HYTUFFR6+TPyHyxPWjUdh48il0Blebg9p1cCc=
|
||||
github.com/DataDog/appsec-internal-go v1.13.0 h1:aO6DmHYsAU8BNFuvYJByhMKGgcQT3WAbj9J/sgAJxtA=
|
||||
@@ -173,8 +172,6 @@ github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 h1:aQ3y1lwWyqYPiWZThqv
|
||||
github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
|
||||
github.com/corona10/goimagehash v1.1.0 h1:teNMX/1e+Wn/AYSbLHX8mj+mF9r60R1kBeqE9MkoYwI=
|
||||
github.com/corona10/goimagehash v1.1.0/go.mod h1:VkvE0mLn84L4aF8vCb6mafVajEb6QYMHl2ZJLn0mOGI=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
@@ -397,8 +394,6 @@ github.com/richardartoul/molecule v1.0.1-0.20240531184615-7ca0df43c0b3 h1:4+LEVO
|
||||
github.com/richardartoul/molecule v1.0.1-0.20240531184615-7ca0df43c0b3/go.mod h1:vl5+MqJ1nBINuSsUI2mGgH79UweUT/B5Fy8857PqyyI=
|
||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 h1:GHRpF1pTW19a8tTFrMLUcfWwyC0pnifVo2ClaLq+hP8=
|
||||
github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46/go.mod h1:uAQ5PCi+MFsC7HjREoAz1BU+Mq60+05gifQSsHSDG/8=
|
||||
github.com/secure-systems-lab/go-securesystemslib v0.9.0 h1:rf1HIbL64nUpEIZnjLZ3mcNEL9NBPB0iuVjyxvq3LZc=
|
||||
@@ -446,8 +441,6 @@ github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfj
|
||||
github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ=
|
||||
github.com/trimmer-io/go-xmp v1.0.0 h1:zY8bolSga5kOjBAaHS6hrdxLgEoYuT875xTy0QDwZWs=
|
||||
github.com/trimmer-io/go-xmp v1.0.0/go.mod h1:Aaptr9sp1lLv7UnCAdQ+gSHZyY2miYaKmcNVj7HRBwA=
|
||||
github.com/urfave/cli v1.22.17 h1:SYzXoiPfQjHBbkYxbew5prZHS1TOLT3ierW8SYLqtVQ=
|
||||
github.com/urfave/cli v1.22.17/go.mod h1:b0ht0aqgH/6pBYzzxURyrM4xXNgsoT/n2ZzwQiEhNVo=
|
||||
github.com/urfave/cli/v3 v3.4.1 h1:1M9UOCy5bLmGnuu1yn3t3CB4rG79Rtoxuv1sPhnm6qM=
|
||||
github.com/urfave/cli/v3 v3.4.1/go.mod h1:FJSKtM/9AiiTOJL4fJ6TbMUkxBXn7GO9guZqoZtpYpo=
|
||||
github.com/vmihailenco/msgpack/v4 v4.3.13 h1:A2wsiTbvp63ilDaWmsk2wjx6xZdxQOvpiNlKBGKKXKI=
|
||||
@@ -582,8 +575,6 @@ golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o=
|
||||
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=
|
||||
golang.org/x/image v0.28.0 h1:gdem5JW1OLS4FbkWgLO+7ZeFzYtL3xClb97GaUzYMFE=
|
||||
golang.org/x/image v0.28.0/go.mod h1:GUJYXtnGKEUgggyzh+Vxt+AviiCcyiwpsl8iQ8MvwGY=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
|
@@ -26,7 +26,7 @@ type HandlerContext interface {
|
||||
WatermarkImage() auximageprovider.Provider
|
||||
ImageDataFactory() *imagedata.Factory
|
||||
Security() *security.Checker
|
||||
ProcessingOptionsFactory() *options.Factory
|
||||
OptionsFactory() *options.Factory
|
||||
}
|
||||
|
||||
// Handler handles image processing requests
|
||||
@@ -109,7 +109,7 @@ func (h *Handler) newRequest(
|
||||
}
|
||||
|
||||
// parse image url and processing options
|
||||
po, imageURL, err := h.HandlerContext.ProcessingOptionsFactory().ParsePath(path, req.Header)
|
||||
po, imageURL, err := h.OptionsFactory().ParsePath(path, req.Header)
|
||||
if err != nil {
|
||||
return "", nil, nil, ierrors.Wrap(err, 0, ierrors.WithCategory(handlers.CategoryPathParsing))
|
||||
}
|
||||
|
@@ -157,7 +157,7 @@ func (r *request) getFallbackImage(
|
||||
// processImage calls actual image processing
|
||||
func (r *request) processImage(ctx context.Context, originData imagedata.ImageData) (*processing.Result, error) {
|
||||
defer monitoring.StartProcessingSegment(ctx, r.monitoringMeta.Filter(monitoring.MetaProcessingOptions))()
|
||||
return processing.ProcessImage(ctx, originData, r.po, r.WatermarkImage(), r.ProcessingOptionsFactory())
|
||||
return processing.ProcessImage(ctx, originData, r.po, r.WatermarkImage())
|
||||
}
|
||||
|
||||
// writeDebugHeaders writes debug headers (X-Origin-*, X-Result-*) to the response
|
||||
|
10
imgproxy.go
10
imgproxy.go
@@ -42,7 +42,7 @@ type Imgproxy struct {
|
||||
imageDataFactory *imagedata.Factory
|
||||
handlers ImgproxyHandlers
|
||||
security *security.Checker
|
||||
processingOptionsFactory *options.Factory
|
||||
optionsFactory *options.Factory
|
||||
config *Config
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ func New(ctx context.Context, config *Config) (*Imgproxy, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
processingOptionsFactory, err := options.NewFactory(&config.ProcessingOptions, security)
|
||||
processingOptionsFactory, err := options.NewFactory(&config.Options, security)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -88,7 +88,7 @@ func New(ctx context.Context, config *Config) (*Imgproxy, error) {
|
||||
imageDataFactory: idf,
|
||||
config: config,
|
||||
security: security,
|
||||
processingOptionsFactory: processingOptionsFactory,
|
||||
optionsFactory: processingOptionsFactory,
|
||||
}
|
||||
|
||||
imgproxy.handlers.Health = healthhandler.New()
|
||||
@@ -208,6 +208,6 @@ func (i *Imgproxy) Security() *security.Checker {
|
||||
return i.security
|
||||
}
|
||||
|
||||
func (i *Imgproxy) ProcessingOptionsFactory() *options.Factory {
|
||||
return i.processingOptionsFactory
|
||||
func (i *Imgproxy) OptionsFactory() *options.Factory {
|
||||
return i.optionsFactory
|
||||
}
|
||||
|
@@ -168,7 +168,7 @@ func (s *ProcessingHandlerTestSuite) TestResultingFormatNotSupported() {
|
||||
}
|
||||
|
||||
func (s *ProcessingHandlerTestSuite) TestSkipProcessingConfig() {
|
||||
s.Config().ProcessingOptions.SkipProcessingFormats = []imagetype.Type{imagetype.PNG}
|
||||
s.Config().Options.SkipProcessingFormats = []imagetype.Type{imagetype.PNG}
|
||||
|
||||
res := s.GET("/unsafe/rs:fill:4:4/plain/local:///test1.png")
|
||||
|
||||
@@ -184,7 +184,7 @@ func (s *ProcessingHandlerTestSuite) TestSkipProcessingPO() {
|
||||
}
|
||||
|
||||
func (s *ProcessingHandlerTestSuite) TestSkipProcessingSameFormat() {
|
||||
s.Config().ProcessingOptions.SkipProcessingFormats = []imagetype.Type{imagetype.PNG}
|
||||
s.Config().Options.SkipProcessingFormats = []imagetype.Type{imagetype.PNG}
|
||||
|
||||
res := s.GET("/unsafe/rs:fill:4:4/plain/local:///test1.png@png")
|
||||
|
||||
@@ -477,7 +477,7 @@ func (s *ProcessingHandlerTestSuite) TestAlwaysRasterizeSvg() {
|
||||
|
||||
func (s *ProcessingHandlerTestSuite) TestAlwaysRasterizeSvgWithEnforceAvif() {
|
||||
config.AlwaysRasterizeSvg = true
|
||||
s.Config().ProcessingOptions.EnforceWebp = true
|
||||
s.Config().Options.EnforceWebp = true
|
||||
|
||||
res := s.GET("/unsafe/plain/local:///test1.svg", http.Header{"Accept": []string{"image/webp"}})
|
||||
|
||||
@@ -487,7 +487,7 @@ func (s *ProcessingHandlerTestSuite) TestAlwaysRasterizeSvgWithEnforceAvif() {
|
||||
|
||||
func (s *ProcessingHandlerTestSuite) TestAlwaysRasterizeSvgDisabled() {
|
||||
config.AlwaysRasterizeSvg = false
|
||||
s.Config().ProcessingOptions.EnforceWebp = true
|
||||
s.Config().Options.EnforceWebp = true
|
||||
|
||||
res := s.GET("/unsafe/plain/local:///test1.svg")
|
||||
|
||||
@@ -497,7 +497,7 @@ func (s *ProcessingHandlerTestSuite) TestAlwaysRasterizeSvgDisabled() {
|
||||
|
||||
func (s *ProcessingHandlerTestSuite) TestAlwaysRasterizeSvgWithFormat() {
|
||||
config.AlwaysRasterizeSvg = true
|
||||
s.Config().ProcessingOptions.SkipProcessingFormats = []imagetype.Type{imagetype.SVG}
|
||||
s.Config().Options.SkipProcessingFormats = []imagetype.Type{imagetype.SVG}
|
||||
|
||||
res := s.GET("/unsafe/plain/local:///test1.svg@svg")
|
||||
|
||||
|
@@ -4,6 +4,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"maps"
|
||||
"slices"
|
||||
|
||||
"github.com/imgproxy/imgproxy/v3/config"
|
||||
"github.com/imgproxy/imgproxy/v3/ensure"
|
||||
@@ -107,19 +108,18 @@ func LoadConfigFromEnv(c *Config) (*Config, error) {
|
||||
c.ReturnAttachment = config.ReturnAttachment
|
||||
|
||||
// Image processing formats
|
||||
copy(c.SkipProcessingFormats, config.SkipProcessingFormats)
|
||||
c.SkipProcessingFormats = slices.Clone(config.SkipProcessingFormats)
|
||||
|
||||
// Presets configuration
|
||||
copy(c.Presets, config.Presets)
|
||||
c.Presets = slices.Clone(config.Presets)
|
||||
c.OnlyPresets = config.OnlyPresets
|
||||
|
||||
// Quality settings
|
||||
c.Quality = config.Quality
|
||||
c.FormatQuality = make(map[imagetype.Type]int, len(config.FormatQuality))
|
||||
maps.Copy(c.FormatQuality, config.FormatQuality)
|
||||
c.FormatQuality = maps.Clone(config.FormatQuality)
|
||||
|
||||
// Security and validation
|
||||
copy(c.AllowedProcessingOptions, config.AllowedProcessingOptions)
|
||||
c.AllowedProcessingOptions = slices.Clone(config.AllowedProcessingOptions)
|
||||
|
||||
// Format preference and enforcement
|
||||
c.AutoWebp = config.AutoWebp
|
||||
@@ -135,9 +135,8 @@ func LoadConfigFromEnv(c *Config) (*Config, error) {
|
||||
// URL processing
|
||||
c.ArgumentsSeparator = config.ArgumentsSeparator
|
||||
c.BaseURL = config.BaseURL
|
||||
c.URLReplacements = append([]URLReplacement(nil), config.URLReplacements...)
|
||||
c.URLReplacements = slices.Clone(config.URLReplacements)
|
||||
c.Base64URLIncludesFilename = config.Base64URLIncludesFilename
|
||||
c.Presets = config.Presets
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
@@ -1,11 +1,7 @@
|
||||
package options
|
||||
|
||||
import (
|
||||
"maps"
|
||||
|
||||
"github.com/imgproxy/imgproxy/v3/imagetype"
|
||||
"github.com/imgproxy/imgproxy/v3/security"
|
||||
"github.com/imgproxy/imgproxy/v3/vips"
|
||||
)
|
||||
|
||||
// Presets is a map of preset names to their corresponding urlOptions
|
||||
@@ -16,6 +12,7 @@ type Factory struct {
|
||||
config *Config // Factory configuration
|
||||
security *security.Checker // Security checker for generating security options
|
||||
presets Presets // Parsed presets
|
||||
defaultPO *ProcessingOptions // Default processing options
|
||||
}
|
||||
|
||||
// NewFactory creates new Factory instance
|
||||
@@ -28,6 +25,7 @@ func NewFactory(config *Config, security *security.Checker) (*Factory, error) {
|
||||
config: config,
|
||||
security: security,
|
||||
presets: make(map[string]urlOptions),
|
||||
defaultPO: newDefaultProcessingOptions(config, security),
|
||||
}
|
||||
|
||||
if err := f.parsePresets(); err != nil {
|
||||
@@ -43,47 +41,5 @@ func NewFactory(config *Config, security *security.Checker) (*Factory, error) {
|
||||
|
||||
// NewProcessingOptions creates new ProcessingOptions instance
|
||||
func (f *Factory) NewProcessingOptions() *ProcessingOptions {
|
||||
po := ProcessingOptions{
|
||||
ResizingType: ResizeFit,
|
||||
Width: 0,
|
||||
Height: 0,
|
||||
ZoomWidth: 1,
|
||||
ZoomHeight: 1,
|
||||
Gravity: GravityOptions{Type: GravityCenter},
|
||||
Enlarge: false,
|
||||
Extend: ExtendOptions{Enabled: false, Gravity: GravityOptions{Type: GravityCenter}},
|
||||
ExtendAspectRatio: ExtendOptions{Enabled: false, Gravity: GravityOptions{Type: GravityCenter}},
|
||||
Padding: PaddingOptions{Enabled: false},
|
||||
Trim: TrimOptions{Enabled: false, Threshold: 10, Smart: true},
|
||||
Rotate: 0,
|
||||
Quality: 0,
|
||||
MaxBytes: 0,
|
||||
Format: imagetype.Unknown,
|
||||
Background: vips.Color{R: 255, G: 255, B: 255},
|
||||
Blur: 0,
|
||||
Sharpen: 0,
|
||||
Dpr: 1,
|
||||
Watermark: WatermarkOptions{Opacity: 1, Position: GravityOptions{Type: GravityCenter}},
|
||||
StripMetadata: f.config.StripMetadata,
|
||||
KeepCopyright: f.config.KeepCopyright,
|
||||
StripColorProfile: f.config.StripColorProfile,
|
||||
AutoRotate: f.config.AutoRotate,
|
||||
EnforceThumbnail: f.config.EnforceThumbnail,
|
||||
ReturnAttachment: f.config.ReturnAttachment,
|
||||
|
||||
SkipProcessingFormats: append([]imagetype.Type(nil), f.config.SkipProcessingFormats...),
|
||||
UsedPresets: make([]string, 0, len(f.config.Presets)),
|
||||
|
||||
SecurityOptions: f.security.NewOptions(),
|
||||
|
||||
// Basically, we need this to update ETag when `IMGPROXY_QUALITY` is changed
|
||||
defaultQuality: f.config.Quality,
|
||||
}
|
||||
|
||||
po.defaultOptions = &po
|
||||
|
||||
po.FormatQuality = make(map[imagetype.Type]int, len(f.config.FormatQuality))
|
||||
maps.Copy(po.FormatQuality, f.config.FormatQuality)
|
||||
|
||||
return &po
|
||||
return f.defaultPO.clone()
|
||||
}
|
||||
|
@@ -16,18 +16,6 @@ func (f *Factory) parsePresets() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// validatePresets validates all presets by applying them to a new ProcessingOptions instance
|
||||
func (f *Factory) validatePresets() error {
|
||||
for name, opts := range f.presets {
|
||||
po := f.NewProcessingOptions()
|
||||
if err := f.applyURLOptions(po, opts, true, name); err != nil {
|
||||
return fmt.Errorf("Error in preset `%s`: %s", name, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// parsePreset parses a preset string and returns the name and options
|
||||
func (f *Factory) parsePreset(presetStr string) error {
|
||||
presetStr = strings.Trim(presetStr, " ")
|
||||
@@ -68,3 +56,15 @@ func (f *Factory) parsePreset(presetStr string) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validatePresets validates all presets by applying them to a new ProcessingOptions instance
|
||||
func (f *Factory) validatePresets() error {
|
||||
for name, opts := range f.presets {
|
||||
po := f.NewProcessingOptions()
|
||||
if err := f.applyURLOptions(po, opts, true, name); err != nil {
|
||||
return fmt.Errorf("Error in preset `%s`: %s", name, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@@ -2,6 +2,7 @@ package options
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"maps"
|
||||
"net/http"
|
||||
"slices"
|
||||
"strconv"
|
||||
@@ -119,6 +120,46 @@ type ProcessingOptions struct {
|
||||
defaultOptions *ProcessingOptions
|
||||
}
|
||||
|
||||
func newDefaultProcessingOptions(config *Config, security *security.Checker) *ProcessingOptions {
|
||||
po := ProcessingOptions{
|
||||
ResizingType: ResizeFit,
|
||||
Width: 0,
|
||||
Height: 0,
|
||||
ZoomWidth: 1,
|
||||
ZoomHeight: 1,
|
||||
Gravity: GravityOptions{Type: GravityCenter},
|
||||
Enlarge: false,
|
||||
Extend: ExtendOptions{Enabled: false, Gravity: GravityOptions{Type: GravityCenter}},
|
||||
ExtendAspectRatio: ExtendOptions{Enabled: false, Gravity: GravityOptions{Type: GravityCenter}},
|
||||
Padding: PaddingOptions{Enabled: false},
|
||||
Trim: TrimOptions{Enabled: false, Threshold: 10, Smart: true},
|
||||
Rotate: 0,
|
||||
Quality: 0,
|
||||
FormatQuality: maps.Clone(config.FormatQuality),
|
||||
MaxBytes: 0,
|
||||
Format: imagetype.Unknown,
|
||||
Background: vips.Color{R: 255, G: 255, B: 255},
|
||||
Blur: 0,
|
||||
Sharpen: 0,
|
||||
Dpr: 1,
|
||||
Watermark: WatermarkOptions{Opacity: 1, Position: GravityOptions{Type: GravityCenter}},
|
||||
StripMetadata: config.StripMetadata,
|
||||
KeepCopyright: config.KeepCopyright,
|
||||
StripColorProfile: config.StripColorProfile,
|
||||
AutoRotate: config.AutoRotate,
|
||||
EnforceThumbnail: config.EnforceThumbnail,
|
||||
ReturnAttachment: config.ReturnAttachment,
|
||||
|
||||
SkipProcessingFormats: slices.Clone(config.SkipProcessingFormats),
|
||||
|
||||
SecurityOptions: security.NewOptions(),
|
||||
|
||||
defaultQuality: config.Quality,
|
||||
}
|
||||
|
||||
return &po
|
||||
}
|
||||
|
||||
func (po *ProcessingOptions) GetQuality() int {
|
||||
q := po.Quality
|
||||
|
||||
@@ -145,6 +186,34 @@ func (po *ProcessingOptions) MarshalJSON() ([]byte, error) {
|
||||
return po.Diff().MarshalJSON()
|
||||
}
|
||||
|
||||
// Default returns the ProcessingOptions instance with defaults set
|
||||
func (po *ProcessingOptions) Default() *ProcessingOptions {
|
||||
return po.defaultOptions.clone()
|
||||
}
|
||||
|
||||
// clone clones ProcessingOptions struct and its slices and maps
|
||||
func (po *ProcessingOptions) clone() *ProcessingOptions {
|
||||
clone := *po
|
||||
|
||||
clone.FormatQuality = maps.Clone(po.FormatQuality)
|
||||
clone.SkipProcessingFormats = slices.Clone(po.SkipProcessingFormats)
|
||||
clone.UsedPresets = slices.Clone(po.UsedPresets)
|
||||
|
||||
if po.Expires != nil {
|
||||
poExipres := *po.Expires
|
||||
clone.Expires = &poExipres
|
||||
}
|
||||
|
||||
// Copy the pointer to the default options struct from parent.
|
||||
// Nil means that we have just cloned the default options struct itself
|
||||
// so we set it as default options.
|
||||
if clone.defaultOptions == nil {
|
||||
clone.defaultOptions = po
|
||||
}
|
||||
|
||||
return &clone
|
||||
}
|
||||
|
||||
func parseDimension(d *int, name, arg string) error {
|
||||
if v, err := strconv.Atoi(arg); err == nil && v >= 0 {
|
||||
*d = v
|
||||
|
@@ -8,6 +8,7 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/imgproxy/imgproxy/v3/config"
|
||||
"github.com/imgproxy/imgproxy/v3/imagetype"
|
||||
@@ -58,11 +59,6 @@ func (s *ProcessingOptionsTestSuite) SetupSuite() {
|
||||
)
|
||||
}
|
||||
|
||||
func (s *ProcessingOptionsTestSuite) SetupTest() {
|
||||
// NOTE: This is temporary, remove once finish img-57-po
|
||||
config.Reset()
|
||||
}
|
||||
|
||||
func (s *ProcessingOptionsTestSuite) SetupSubTest() {
|
||||
s.ResetLazyObjects()
|
||||
}
|
||||
@@ -520,7 +516,6 @@ func (s *ProcessingOptionsTestSuite) TestParsePathWebpDetection() {
|
||||
|
||||
func (s *ProcessingOptionsTestSuite) TestParsePathWebpEnforce() {
|
||||
s.config().EnforceWebp = true
|
||||
config.EnforceWebp = true // TODO: REMOVE, THIS IS TEMP
|
||||
|
||||
path := "/plain/http://images.dev/lorem/ipsum.jpg@png"
|
||||
headers := http.Header{"Accept": []string{"image/webp"}}
|
||||
@@ -533,8 +528,7 @@ func (s *ProcessingOptionsTestSuite) TestParsePathWebpEnforce() {
|
||||
}
|
||||
|
||||
func (s *ProcessingOptionsTestSuite) TestParsePathWidthHeader() {
|
||||
s.config().EnableClientHints = true // TODO: REMOVE
|
||||
config.EnableClientHints = true // TODO: REMOVE
|
||||
s.config().EnableClientHints = true
|
||||
|
||||
path := "/plain/http://images.dev/lorem/ipsum.jpg@png"
|
||||
headers := http.Header{"Width": []string{"100"}}
|
||||
@@ -691,6 +685,23 @@ func (s *ProcessingOptionsTestSuite) TestParseAllowedOptions() {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ProcessingOptionsTestSuite) TestProcessingOptionsClone() {
|
||||
now := time.Now()
|
||||
|
||||
// Create ProcessingOptions using factory
|
||||
original := s.factory().NewProcessingOptions()
|
||||
original.SkipProcessingFormats = []imagetype.Type{
|
||||
imagetype.PNG, imagetype.JPEG,
|
||||
}
|
||||
original.UsedPresets = []string{"preset1", "preset2"}
|
||||
original.Expires = &now
|
||||
|
||||
// Clone the original
|
||||
cloned := original.clone()
|
||||
|
||||
testutil.EqualButNotSame(s.T(), original, cloned)
|
||||
}
|
||||
|
||||
func TestProcessingOptions(t *testing.T) {
|
||||
suite.Run(t, new(ProcessingOptionsTestSuite))
|
||||
}
|
||||
|
@@ -19,8 +19,6 @@ type pipelineContext struct {
|
||||
// The watermark image provider, if any watermarking is to be done.
|
||||
watermarkProvider auximageprovider.Provider
|
||||
|
||||
processingOptionsFactory *options.Factory
|
||||
|
||||
trimmed bool
|
||||
|
||||
srcWidth int
|
||||
@@ -79,7 +77,6 @@ func (p pipeline) Run(
|
||||
po *options.ProcessingOptions,
|
||||
imgdata imagedata.ImageData,
|
||||
watermark auximageprovider.Provider,
|
||||
processingOptionsFactory *options.Factory,
|
||||
) error {
|
||||
pctx := pipelineContext{
|
||||
ctx: ctx,
|
||||
@@ -92,7 +89,6 @@ func (p pipeline) Run(
|
||||
|
||||
cropGravity: po.Crop.Gravity,
|
||||
watermarkProvider: watermark,
|
||||
processingOptionsFactory: processingOptionsFactory,
|
||||
}
|
||||
|
||||
if pctx.cropGravity.Type == options.GravityUnknown {
|
||||
|
@@ -85,7 +85,6 @@ func ProcessImage(
|
||||
imgdata imagedata.ImageData,
|
||||
po *options.ProcessingOptions,
|
||||
watermarkProvider auximageprovider.Provider,
|
||||
processingOptionsFactory *options.Factory,
|
||||
) (*Result, error) {
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
@@ -139,12 +138,12 @@ func ProcessImage(
|
||||
}
|
||||
|
||||
// Transform the image (resize, crop, etc)
|
||||
if err = transformImage(ctx, img, po, imgdata, animated, watermarkProvider, processingOptionsFactory); err != nil {
|
||||
if err = transformImage(ctx, img, po, imgdata, animated, watermarkProvider); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Finalize the image (colorspace conversion, metadata stripping, etc)
|
||||
if err = finalizePipeline.Run(ctx, img, po, imgdata, watermarkProvider, processingOptionsFactory); err != nil {
|
||||
if err = finalizePipeline.Run(ctx, img, po, imgdata, watermarkProvider); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -389,13 +388,12 @@ func transformImage(
|
||||
imgdata imagedata.ImageData,
|
||||
asAnimated bool,
|
||||
watermark auximageprovider.Provider,
|
||||
processingOptionsFactory *options.Factory,
|
||||
) error {
|
||||
if asAnimated {
|
||||
return transformAnimated(ctx, img, po, watermark, processingOptionsFactory)
|
||||
return transformAnimated(ctx, img, po, watermark)
|
||||
}
|
||||
|
||||
return mainPipeline.Run(ctx, img, po, imgdata, watermark, processingOptionsFactory)
|
||||
return mainPipeline.Run(ctx, img, po, imgdata, watermark)
|
||||
}
|
||||
|
||||
func transformAnimated(
|
||||
@@ -403,7 +401,6 @@ func transformAnimated(
|
||||
img *vips.Image,
|
||||
po *options.ProcessingOptions,
|
||||
watermark auximageprovider.Provider,
|
||||
processingOptionsFactory *options.Factory,
|
||||
) error {
|
||||
if po.Trim.Enabled {
|
||||
log.Warning("Trim is not supported for animated images")
|
||||
@@ -458,7 +455,7 @@ func transformAnimated(
|
||||
// Transform the frame using the main pipeline.
|
||||
// We don't provide imgdata here to prevent scale-on-load.
|
||||
// Let's skip passing watermark here since in would be applied later to all frames at once.
|
||||
if err = mainPipeline.Run(ctx, frame, po, nil, nil, processingOptionsFactory); err != nil {
|
||||
if err = mainPipeline.Run(ctx, frame, po, nil, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -490,7 +487,7 @@ func transformAnimated(
|
||||
dprScale = 1.0
|
||||
}
|
||||
|
||||
if err = applyWatermark(ctx, img, watermark, processingOptionsFactory, po, dprScale, framesCount); err != nil {
|
||||
if err = applyWatermark(ctx, img, watermark, po, dprScale, framesCount); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@@ -103,7 +103,7 @@ func (s *ProcessingTestSuite) TestResizeToFit() {
|
||||
po.Width = tc.width
|
||||
po.Height = tc.height
|
||||
|
||||
result, err := ProcessImage(context.Background(), imgdata, po, nil, s.pof)
|
||||
result, err := ProcessImage(context.Background(), imgdata, po, nil)
|
||||
s.Require().NoError(err)
|
||||
s.Require().NotNil(result)
|
||||
|
||||
@@ -142,7 +142,7 @@ func (s *ProcessingTestSuite) TestResizeToFitEnlarge() {
|
||||
po.Width = tc.width
|
||||
po.Height = tc.height
|
||||
|
||||
result, err := ProcessImage(context.Background(), imgdata, po, nil, s.pof)
|
||||
result, err := ProcessImage(context.Background(), imgdata, po, nil)
|
||||
s.Require().NoError(err)
|
||||
s.Require().NotNil(result)
|
||||
|
||||
@@ -186,7 +186,7 @@ func (s *ProcessingTestSuite) TestResizeToFitExtend() {
|
||||
po.Width = tc.width
|
||||
po.Height = tc.height
|
||||
|
||||
result, err := ProcessImage(context.Background(), imgdata, po, nil, s.pof)
|
||||
result, err := ProcessImage(context.Background(), imgdata, po, nil)
|
||||
s.Require().NoError(err)
|
||||
s.Require().NotNil(result)
|
||||
|
||||
@@ -230,7 +230,7 @@ func (s *ProcessingTestSuite) TestResizeToFitExtendAR() {
|
||||
po.Width = tc.width
|
||||
po.Height = tc.height
|
||||
|
||||
result, err := ProcessImage(context.Background(), imgdata, po, nil, s.pof)
|
||||
result, err := ProcessImage(context.Background(), imgdata, po, nil)
|
||||
s.Require().NoError(err)
|
||||
s.Require().NotNil(result)
|
||||
|
||||
@@ -268,7 +268,7 @@ func (s *ProcessingTestSuite) TestResizeToFill() {
|
||||
po.Width = tc.width
|
||||
po.Height = tc.height
|
||||
|
||||
result, err := ProcessImage(context.Background(), imgdata, po, nil, s.pof)
|
||||
result, err := ProcessImage(context.Background(), imgdata, po, nil)
|
||||
s.Require().NoError(err)
|
||||
s.Require().NotNil(result)
|
||||
|
||||
@@ -307,7 +307,7 @@ func (s *ProcessingTestSuite) TestResizeToFillEnlarge() {
|
||||
po.Width = tc.width
|
||||
po.Height = tc.height
|
||||
|
||||
result, err := ProcessImage(context.Background(), imgdata, po, nil, s.pof)
|
||||
result, err := ProcessImage(context.Background(), imgdata, po, nil)
|
||||
s.Require().NoError(err)
|
||||
s.Require().NotNil(result)
|
||||
|
||||
@@ -353,7 +353,7 @@ func (s *ProcessingTestSuite) TestResizeToFillExtend() {
|
||||
po.Width = tc.width
|
||||
po.Height = tc.height
|
||||
|
||||
result, err := ProcessImage(context.Background(), imgdata, po, nil, s.pof)
|
||||
result, err := ProcessImage(context.Background(), imgdata, po, nil)
|
||||
s.Require().NoError(err)
|
||||
s.Require().NotNil(result)
|
||||
|
||||
@@ -399,7 +399,7 @@ func (s *ProcessingTestSuite) TestResizeToFillExtendAR() {
|
||||
po.Width = tc.width
|
||||
po.Height = tc.height
|
||||
|
||||
result, err := ProcessImage(context.Background(), imgdata, po, nil, s.pof)
|
||||
result, err := ProcessImage(context.Background(), imgdata, po, nil)
|
||||
s.Require().NoError(err)
|
||||
s.Require().NotNil(result)
|
||||
|
||||
@@ -437,7 +437,7 @@ func (s *ProcessingTestSuite) TestResizeToFillDown() {
|
||||
po.Width = tc.width
|
||||
po.Height = tc.height
|
||||
|
||||
result, err := ProcessImage(context.Background(), imgdata, po, nil, s.pof)
|
||||
result, err := ProcessImage(context.Background(), imgdata, po, nil)
|
||||
s.Require().NoError(err)
|
||||
s.Require().NotNil(result)
|
||||
|
||||
@@ -476,7 +476,7 @@ func (s *ProcessingTestSuite) TestResizeToFillDownEnlarge() {
|
||||
po.Width = tc.width
|
||||
po.Height = tc.height
|
||||
|
||||
result, err := ProcessImage(context.Background(), imgdata, po, nil, s.pof)
|
||||
result, err := ProcessImage(context.Background(), imgdata, po, nil)
|
||||
s.Require().NoError(err)
|
||||
s.Require().NotNil(result)
|
||||
|
||||
@@ -522,7 +522,7 @@ func (s *ProcessingTestSuite) TestResizeToFillDownExtend() {
|
||||
po.Width = tc.width
|
||||
po.Height = tc.height
|
||||
|
||||
result, err := ProcessImage(context.Background(), imgdata, po, nil, s.pof)
|
||||
result, err := ProcessImage(context.Background(), imgdata, po, nil)
|
||||
s.Require().NoError(err)
|
||||
s.Require().NotNil(result)
|
||||
|
||||
@@ -566,7 +566,7 @@ func (s *ProcessingTestSuite) TestResizeToFillDownExtendAR() {
|
||||
po.Width = tc.width
|
||||
po.Height = tc.height
|
||||
|
||||
result, err := ProcessImage(context.Background(), imgdata, po, nil, s.pof)
|
||||
result, err := ProcessImage(context.Background(), imgdata, po, nil)
|
||||
s.Require().NoError(err)
|
||||
s.Require().NotNil(result)
|
||||
|
||||
@@ -995,7 +995,7 @@ func (s *ProcessingTestSuite) TestResultSizeLimit() {
|
||||
po.Rotate = tc.rotate
|
||||
po.Padding = tc.padding
|
||||
|
||||
result, err := ProcessImage(context.Background(), imgdata, po, nil, s.pof)
|
||||
result, err := ProcessImage(context.Background(), imgdata, po, nil)
|
||||
|
||||
s.Require().NoError(err)
|
||||
s.Require().NotNil(result)
|
||||
@@ -1010,7 +1010,7 @@ func (s *ProcessingTestSuite) TestImageResolutionTooLarge() {
|
||||
po.SecurityOptions.MaxSrcResolution = 1
|
||||
|
||||
imgdata := s.openFile("test2.jpg")
|
||||
_, err := ProcessImage(context.Background(), imgdata, po, nil, s.pof)
|
||||
_, err := ProcessImage(context.Background(), imgdata, po, nil)
|
||||
|
||||
s.Require().Error(err)
|
||||
s.Require().Equal(422, ierrors.Wrap(err, 0).StatusCode())
|
||||
|
@@ -22,20 +22,22 @@ var watermarkPipeline = pipeline{
|
||||
padding,
|
||||
}
|
||||
|
||||
func prepareWatermark(wm *vips.Image, wmData imagedata.ImageData, opts *options.WatermarkOptions, pof *options.Factory, imgWidth, imgHeight int, offsetScale float64, framesCount int) error {
|
||||
func prepareWatermark(wm *vips.Image, wmData imagedata.ImageData, po *options.ProcessingOptions, imgWidth, imgHeight int, offsetScale float64, framesCount int) error {
|
||||
if err := wm.Load(wmData, 1, 1.0, 1); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
po := pof.NewProcessingOptions()
|
||||
po.ResizingType = options.ResizeFit
|
||||
po.Dpr = 1
|
||||
po.Enlarge = true
|
||||
po.Format = wmData.Format()
|
||||
opts := po.Watermark
|
||||
|
||||
wmPo := po.Default()
|
||||
wmPo.ResizingType = options.ResizeFit
|
||||
wmPo.Dpr = 1
|
||||
wmPo.Enlarge = true
|
||||
wmPo.Format = wmData.Format()
|
||||
|
||||
if opts.Scale > 0 {
|
||||
po.Width = max(imath.ScaleToEven(imgWidth, opts.Scale), 1)
|
||||
po.Height = max(imath.ScaleToEven(imgHeight, opts.Scale), 1)
|
||||
wmPo.Width = max(imath.ScaleToEven(imgWidth, opts.Scale), 1)
|
||||
wmPo.Height = max(imath.ScaleToEven(imgHeight, opts.Scale), 1)
|
||||
}
|
||||
|
||||
if opts.ShouldReplicate() {
|
||||
@@ -53,14 +55,14 @@ func prepareWatermark(wm *vips.Image, wmData imagedata.ImageData, opts *options.
|
||||
offY = imath.ScaleToEven(imgHeight, opts.Position.Y)
|
||||
}
|
||||
|
||||
po.Padding.Enabled = true
|
||||
po.Padding.Left = offX / 2
|
||||
po.Padding.Right = offX - po.Padding.Left
|
||||
po.Padding.Top = offY / 2
|
||||
po.Padding.Bottom = offY - po.Padding.Top
|
||||
wmPo.Padding.Enabled = true
|
||||
wmPo.Padding.Left = offX / 2
|
||||
wmPo.Padding.Right = offX - wmPo.Padding.Left
|
||||
wmPo.Padding.Top = offY / 2
|
||||
wmPo.Padding.Bottom = offY - wmPo.Padding.Top
|
||||
}
|
||||
|
||||
if err := watermarkPipeline.Run(context.Background(), wm, po, wmData, nil, pof); err != nil {
|
||||
if err := watermarkPipeline.Run(context.Background(), wm, wmPo, wmData, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -84,7 +86,6 @@ func applyWatermark(
|
||||
ctx context.Context,
|
||||
img *vips.Image,
|
||||
watermark auximageprovider.Provider,
|
||||
pof *options.Factory,
|
||||
po *options.ProcessingOptions,
|
||||
offsetScale float64,
|
||||
framesCount int,
|
||||
@@ -111,7 +112,7 @@ func applyWatermark(
|
||||
height := img.Height()
|
||||
frameHeight := height / framesCount
|
||||
|
||||
if err := prepareWatermark(wm, wmData, &opts, pof, width, frameHeight, offsetScale, framesCount); err != nil {
|
||||
if err := prepareWatermark(wm, wmData, po, width, frameHeight, offsetScale, framesCount); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -194,5 +195,5 @@ func watermark(
|
||||
return nil
|
||||
}
|
||||
|
||||
return applyWatermark(pctx.ctx, img, pctx.watermarkProvider, pctx.processingOptionsFactory, po, pctx.dprScale, 1)
|
||||
return applyWatermark(pctx.ctx, img, pctx.watermarkProvider, po, pctx.dprScale, 1)
|
||||
}
|
||||
|
104
testutil/equal_but_not_same.go
Normal file
104
testutil/equal_but_not_same.go
Normal file
@@ -0,0 +1,104 @@
|
||||
package testutil
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// EqualButNotSame asserts that expected and actual objects are not the same.
|
||||
// It recursively checks all fields to ensure that no pointers are shared.
|
||||
// If a pointer, slice or map are nil in either object, the test fails.
|
||||
func EqualButNotSame(t *testing.T, expected, actual any) {
|
||||
t.Helper()
|
||||
|
||||
expectedVal := reflect.ValueOf(expected)
|
||||
actualVal := reflect.ValueOf(actual)
|
||||
|
||||
deepEqual(t, expectedVal, actualVal, "")
|
||||
}
|
||||
|
||||
// deepEqual recursively verifies that all values are equal but pointers are different
|
||||
// except for the Expires field which is explicitly allowed to be shared
|
||||
func deepEqual(t *testing.T, left, right reflect.Value, fieldPath string) {
|
||||
require.True(t, left.IsValid() && right.IsValid(), "invalid value at %s", fieldPath)
|
||||
require.Equal(t, left.Type(), right.Type(), "types are not equal at %s", fieldPath)
|
||||
|
||||
switch left.Kind() {
|
||||
case reflect.Ptr:
|
||||
// Pointers should not be nil and must point to different objects
|
||||
require.False(t, left.IsNil(), "nil pointer at %s (left)", fieldPath)
|
||||
require.False(t, right.IsNil(), "nil pointer at %s (right)", fieldPath)
|
||||
require.NotSame(t, left.Interface(), right.Interface(), "shared pointer at %s", fieldPath)
|
||||
|
||||
deepEqual(t, left.Elem(), right.Elem(), fieldPath)
|
||||
|
||||
case reflect.Slice:
|
||||
// Slices should contain some elements and must not share the same underlying array
|
||||
require.Equal(t, left.Len(), right.Len(), "slice length mismatch at %s", fieldPath)
|
||||
require.NotEmpty(t, left.Len(), "slice must not be empty %s (left)", fieldPath)
|
||||
require.NotEmpty(t, right.Len(), "slice must not be empty %s (right)", fieldPath)
|
||||
require.NotEqual(t, left.Pointer(), right.Pointer(), "shared slices at %s", fieldPath)
|
||||
|
||||
// Recursively verify slice elements
|
||||
for i := 0; i < left.Len(); i++ {
|
||||
elemPath := buildPath(fieldPath, "[", anyToString(i), "]")
|
||||
deepEqual(t, left.Index(i), right.Index(i), elemPath)
|
||||
}
|
||||
|
||||
case reflect.Map:
|
||||
// Maps should contain some elements and must not share the same underlying map
|
||||
require.Equal(t, left.Len(), right.Len(), "map length mismatch at %s", fieldPath)
|
||||
require.NotEmpty(t, left.Len(), "map must not be empty %s (left)", fieldPath)
|
||||
require.NotEmpty(t, right.Len(), "map must not be empty %s (right)", fieldPath)
|
||||
require.NotEqual(t, left.Pointer(), right.Pointer(), "shared maps at %s", fieldPath)
|
||||
|
||||
// Recursively verify map values
|
||||
for _, key := range left.MapKeys() {
|
||||
keyStr := anyToString(key.Interface())
|
||||
keyPath := buildPath(fieldPath, "[", keyStr, "]")
|
||||
originalMapVal := left.MapIndex(key)
|
||||
clonedMapVal := right.MapIndex(key)
|
||||
deepEqual(t, originalMapVal, clonedMapVal, keyPath)
|
||||
}
|
||||
|
||||
case reflect.Struct:
|
||||
require.Equal(t, left.Interface(), right.Interface(), "structs are not equal at %s", fieldPath)
|
||||
|
||||
// Fallback to recursive field-by-field comparison
|
||||
for i := 0; i < left.NumField(); i++ {
|
||||
field := left.Type().Field(i)
|
||||
if !field.IsExported() {
|
||||
continue // Skip unexported fields
|
||||
}
|
||||
|
||||
nestedPath := buildPath(fieldPath, ".", field.Name, "")
|
||||
originalFieldVal := left.Field(i)
|
||||
clonedFieldVal := right.Field(i)
|
||||
deepEqual(t, originalFieldVal, clonedFieldVal, nestedPath)
|
||||
}
|
||||
|
||||
default:
|
||||
// For primitive types, just verify equality
|
||||
require.Equal(t, left.Interface(), right.Interface(), "values not equal at %s", fieldPath)
|
||||
}
|
||||
}
|
||||
|
||||
// buildPath builds a field path for error messages
|
||||
func buildPath(basePath, separator, element, suffix string) string {
|
||||
if basePath == "" {
|
||||
return element + suffix
|
||||
}
|
||||
return basePath + separator + element + suffix
|
||||
}
|
||||
|
||||
// anyToString converts an any to a string for path building
|
||||
func anyToString(v any) string {
|
||||
switch val := v.(type) {
|
||||
case string:
|
||||
return val
|
||||
default:
|
||||
return reflect.ValueOf(val).String()
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user