瀏覽代碼

Add more options parsing helpers

DarthSim 1 周之前
父節點
當前提交
60289268fa
共有 3 個文件被更改,包括 123 次插入56 次删除
  1. 24 34
      options/apply.go
  2. 8 3
      options/errors.go
  3. 91 19
      options/parse.go

+ 24 - 34
options/apply.go

@@ -1,9 +1,9 @@
 package options
 
 import (
-	"encoding/base64"
 	"fmt"
 	"log/slog"
+	"maps"
 	"slices"
 	"strconv"
 	"time"
@@ -81,7 +81,9 @@ func applyResizingTypeOption(o *Options, args []string) error {
 	if r, ok := resizeTypes[args[0]]; ok {
 		o.Set(keys.ResizingType, r)
 	} else {
-		return newOptionArgumentError("Invalid %s: %s", keys.ResizingType, args[0])
+		return newInvalidArgumentError(
+			keys.ResizingType, args[0], slices.Collect(maps.Keys(resizeTypes))...,
+		)
 	}
 
 	return nil
@@ -133,7 +135,7 @@ func applyDprOption(o *Options, args []string) error {
 }
 
 func applyGravityOption(o *Options, args []string) error {
-	return parseGravity(o, keys.Gravity, args, cropGravityTypes)
+	return parseGravity(o, keys.Gravity, cropGravityTypes, args...)
 }
 
 func applyCropOption(o *Options, args []string) error {
@@ -148,7 +150,7 @@ func applyCropOption(o *Options, args []string) error {
 	}
 
 	if len(args) > 2 {
-		return parseGravity(o, keys.CropGravity, args[2:], cropGravityTypes)
+		return parseGravity(o, keys.CropGravity, cropGravityTypes, args[2:]...)
 	}
 
 	return nil
@@ -208,10 +210,8 @@ func applyTrimOption(o *Options, args []string) error {
 	}
 
 	if nArgs > 1 && len(args[1]) > 0 {
-		if c, err := color.RGBFromHex(args[1]); err == nil {
-			o.Set(keys.TrimColor, c)
-		} else {
-			return newOptionArgumentError("Invalid %s: %s", keys.TrimColor, args[1])
+		if err := parseHexRGBColor(o, keys.TrimColor, args[1]); err != nil {
+			return err
 		}
 	} else {
 		o.Delete(keys.TrimColor)
@@ -280,10 +280,8 @@ func applyBackgroundOption(o *Options, args []string) error {
 			return nil
 		}
 
-		if c, err := color.RGBFromHex(args[0]); err == nil {
-			o.Set(keys.Background, c)
-		} else {
-			return newOptionArgumentError("Invalid %s argument: %s", keys.Background, err)
+		if err := parseHexRGBColor(o, keys.Background, args[0]); err != nil {
+			return err
 		}
 
 	case 3:
@@ -292,25 +290,25 @@ func applyBackgroundOption(o *Options, args []string) error {
 		if r, err := strconv.ParseUint(args[0], 10, 8); err == nil && r <= 255 {
 			c.R = uint8(r)
 		} else {
-			return newOptionArgumentError("Invalid %s red channel: %s", keys.Background, args[0])
+			return newInvalidArgumentError(keys.Background+".R", args[0], "number in range 0-255")
 		}
 
 		if g, err := strconv.ParseUint(args[1], 10, 8); err == nil && g <= 255 {
 			c.G = uint8(g)
 		} else {
-			return newOptionArgumentError("Invalid %s green channel: %s", keys.Background, args[1])
+			return newInvalidArgumentError(keys.Background+".G", args[1], "number in range 0-255")
 		}
 
 		if b, err := strconv.ParseUint(args[2], 10, 8); err == nil && b <= 255 {
 			c.B = uint8(b)
 		} else {
-			return newOptionArgumentError("Invalid %s blue channel: %s", keys.Background, args[2])
+			return newInvalidArgumentError(keys.Background+".B", args[2], "number in range 0-255")
 		}
 
 		o.Set(keys.Background, c)
 
 	default:
-		return newOptionArgumentError("Invalid %s arguments: %v", keys.Background, args)
+		return newInvalidArgsError(keys.Background, args)
 	}
 
 	return nil
@@ -333,17 +331,15 @@ func applyWatermarkOption(o *Options, args []string) error {
 		return err
 	}
 
-	if wo, err := strconv.ParseFloat(args[0], 64); err == nil && wo >= 0 && wo <= 1 {
-		o.Set(keys.WatermarkOpacity, wo)
-	} else {
-		return newOptionArgumentError("Invalid %s: %s", keys.WatermarkOpacity, args[0])
+	if err := parseOpacityFloat(o, keys.WatermarkOpacity, args[0]); err != nil {
+		return err
 	}
 
 	if len(args) > 1 && len(args[1]) > 0 {
-		if pos, ok := gravityTypes[args[1]]; ok && slices.Contains(watermarkGravityTypes, pos) {
-			o.Set(keys.WatermarkPosition, pos)
-		} else {
-			return newOptionArgumentError("Invalid %s: %s", keys.WatermarkPosition, args[1])
+		if _, err := parseGravityType(
+			o, keys.WatermarkPosition, watermarkGravityTypes, args[1],
+		); err != nil {
+			return err
 		}
 	}
 
@@ -376,7 +372,7 @@ func applyFormatOption(o *Options, args []string) error {
 	if f, ok := imagetype.GetTypeByName(args[0]); ok {
 		o.Set(keys.Format, f)
 	} else {
-		return newOptionArgumentError("Invalid image format: %s", args[0])
+		return newInvalidArgumentError(keys.Format, args[0], "supported image format")
 	}
 
 	return nil
@@ -413,19 +409,13 @@ func applyFilenameOption(o *Options, args []string) error {
 		return err
 	}
 
-	filename := args[0]
-
 	if len(args) > 1 && len(args[1]) > 0 {
 		if encoded, _ := strconv.ParseBool(args[1]); encoded {
-			if decoded, err := base64.RawURLEncoding.DecodeString(filename); err == nil {
-				filename = string(decoded)
-			} else {
-				return newOptionArgumentError("Invalid %s encoding: %s", keys.Filename, err)
-			}
+			return parseBase64String(o, keys.Filename, args[0])
 		}
 	}
 
-	o.Set(keys.Filename, filename)
+	o.Set(keys.Filename, args[0])
 
 	return nil
 }
@@ -437,7 +427,7 @@ func applyExpiresOption(o *Options, args []string) error {
 
 	timestamp, err := strconv.ParseInt(args[0], 10, 64)
 	if err != nil {
-		return newOptionArgumentError("Invalid %s argument: %v", keys.Expires, args[0])
+		return newInvalidArgumentError(keys.Expires, args[0], "unix timestamp")
 	}
 
 	if timestamp > 0 && timestamp < time.Now().Unix() {

+ 8 - 3
options/errors.go

@@ -83,11 +83,16 @@ func newSecurityOptionsError() error {
 func (e SecurityOptionsError) Error() string { return "Security processing options are not allowed" }
 
 // newInvalidArgsError creates a standardized error for invalid arguments
-func newInvalidArgsError(name string, args []string, expected ...string) error {
-	msg := "Invalid %s arguments: %s"
+func newInvalidArgsError(name string, args []string) error {
+	return newOptionArgumentError("Invalid %s arguments: %s", name, args)
+}
+
+// newInvalidArgumentError creates a standardized error for an invalid single argument
+func newInvalidArgumentError(key, arg string, expected ...string) error {
+	msg := "Invalid %s: %s"
 	if len(expected) > 0 {
 		msg += " (expected " + strings.Join(expected, ", ") + ")"
 	}
 
-	return newOptionArgumentError(msg, name, args)
+	return newOptionArgumentError(msg, key, arg)
 }

+ 91 - 19
options/parse.go

@@ -1,12 +1,15 @@
 package options
 
 import (
+	"encoding/base64"
 	"fmt"
 	"log/slog"
 	"slices"
 	"strconv"
+	"strings"
 
 	"github.com/imgproxy/imgproxy/v3/options/keys"
+	"github.com/imgproxy/imgproxy/v3/vips/color"
 )
 
 // ensureMaxArgs checks if the number of arguments is as expected
@@ -58,7 +61,7 @@ func parsePositiveFloat(o *Options, key string, args ...string) error {
 
 	f, err := strconv.ParseFloat(args[0], 64)
 	if err != nil || f < 0 {
-		return newInvalidArgsError(key, args, "positive number or 0")
+		return newInvalidArgumentError(key, args[0], "positive number or 0")
 	}
 
 	o.Set(key, f)
@@ -74,7 +77,7 @@ func parsePositiveNonZeroFloat(o *Options, key string, args ...string) error {
 
 	f, err := strconv.ParseFloat(args[0], 64)
 	if err != nil || f <= 0 {
-		return newInvalidArgsError(key, args, "positive number")
+		return newInvalidArgumentError(key, args[0], "positive number")
 	}
 
 	o.Set(key, f)
@@ -90,7 +93,7 @@ func parseInt(o *Options, key string, args ...string) error {
 
 	i, err := strconv.Atoi(args[0])
 	if err != nil {
-		return newOptionArgumentError(key, args)
+		return newInvalidArgumentError(key, args[0], "integer number")
 	}
 
 	o.Set(key, i)
@@ -106,7 +109,7 @@ func parsePositiveNonZeroInt(o *Options, key string, args ...string) error {
 
 	i, err := strconv.Atoi(args[0])
 	if err != nil || i <= 0 {
-		return newInvalidArgsError(key, args, "positive number")
+		return newInvalidArgumentError(key, args[0], "positive number")
 	}
 
 	o.Set(key, i)
@@ -122,7 +125,7 @@ func parsePositiveInt(o *Options, key string, args ...string) error {
 
 	i, err := strconv.Atoi(args[0])
 	if err != nil || i < 0 {
-		return newOptionArgumentError("Invalid %s arguments: %s (expected positive number)", key, args)
+		return newInvalidArgumentError(key, args[0], "positive number or 0")
 	}
 
 	o.Set(key, i)
@@ -138,7 +141,7 @@ func parseQualityInt(o *Options, key string, args ...string) error {
 
 	i, err := strconv.Atoi(args[0])
 	if err != nil || i < 1 || i > 100 {
-		return newInvalidArgsError(key, args, "number in range 1-100")
+		return newInvalidArgumentError(key, args[0], "number in range 1-100")
 	}
 
 	o.Set(key, i)
@@ -146,6 +149,22 @@ func parseQualityInt(o *Options, key string, args ...string) error {
 	return nil
 }
 
+// parseOpacityFloat parses an opacity float option value (0-1)
+func parseOpacityFloat(o *Options, key string, args ...string) error {
+	if err := ensureMaxArgs(key, args, 1); err != nil {
+		return err
+	}
+
+	f, err := strconv.ParseFloat(args[0], 64)
+	if err != nil || f < 0 || f > 1 {
+		return newInvalidArgumentError(key, args[0], "number in range 0-1")
+	}
+
+	o.Set(key, f)
+
+	return nil
+}
+
 // parseResolution parses a resolution option value in megapixels and stores it as pixels
 func parseResolution(o *Options, key string, args ...string) error {
 	if err := ensureMaxArgs(key, args, 1); err != nil {
@@ -154,7 +173,7 @@ func parseResolution(o *Options, key string, args ...string) error {
 
 	f, err := strconv.ParseFloat(args[0], 64)
 	if err != nil || f < 0 {
-		return newInvalidArgsError(key, args, "positive number or 0")
+		return newInvalidArgumentError(key, args[0], "positive number or 0")
 	}
 
 	// Resolution is defined as megapixels but stored as pixels
@@ -163,15 +182,70 @@ func parseResolution(o *Options, key string, args ...string) error {
 	return nil
 }
 
-func isGravityOffcetValid(gravity GravityType, offset float64) bool {
+// parseBase64String parses a base64-encoded string option value
+func parseBase64String(o *Options, key string, args ...string) error {
+	if err := ensureMaxArgs(key, args, 1); err != nil {
+		return err
+	}
+
+	b, err := base64.RawURLEncoding.DecodeString(strings.TrimRight(args[0], "="))
+	if err != nil {
+		return newInvalidArgumentError(key, args[0], "URL-safe base64-encoded string")
+	}
+
+	o.Set(key, string(b))
+
+	return nil
+}
+
+func parseHexRGBColor(o *Options, key string, args ...string) error {
+	if err := ensureMaxArgs(key, args, 1); err != nil {
+		return err
+	}
+
+	c, err := color.RGBFromHex(args[0])
+	if err != nil {
+		return newInvalidArgumentError(key, args[0], "hex-encoded color")
+	}
+
+	o.Set(key, c)
+
+	return nil
+}
+
+func parseGravityType(
+	o *Options,
+	key string,
+	allowedTypes []GravityType,
+	args ...string,
+) (GravityType, error) {
+	if err := ensureMaxArgs(key, args, 1); err != nil {
+		return GravityUnknown, err
+	}
+
+	gType, ok := gravityTypes[args[0]]
+	if !ok || !slices.Contains(allowedTypes, gType) {
+		types := make([]string, len(allowedTypes))
+		for i, at := range allowedTypes {
+			types[i] = at.String()
+		}
+		return GravityUnknown, newInvalidArgumentError(key, args[0], types...)
+	}
+
+	o.Set(key, gType)
+
+	return gType, nil
+}
+
+func isGravityOffsetValid(gravity GravityType, offset float64) bool {
 	return gravity != GravityFocusPoint || (offset >= 0 && offset <= 1)
 }
 
 func parseGravity(
 	o *Options,
 	key string,
-	args []string,
 	allowedTypes []GravityType,
+	args ...string,
 ) error {
 	nArgs := len(args)
 
@@ -179,11 +253,9 @@ func parseGravity(
 	keyXOffset := key + keys.SuffixXOffset
 	keyYOffset := key + keys.SuffixYOffset
 
-	gType, ok := gravityTypes[args[0]]
-	if ok && slices.Contains(allowedTypes, gType) {
-		o.Set(keyType, gType)
-	} else {
-		return newOptionArgumentError("Invalid %s: %s", keyType, args[0])
+	gType, err := parseGravityType(o, keyType, allowedTypes, args[0])
+	if err != nil {
+		return err
 	}
 
 	switch gType {
@@ -206,18 +278,18 @@ func parseGravity(
 		}
 
 		if nArgs > 1 {
-			if x, err := strconv.ParseFloat(args[1], 64); err == nil && isGravityOffcetValid(gType, x) {
+			if x, err := strconv.ParseFloat(args[1], 64); err == nil && isGravityOffsetValid(gType, x) {
 				o.Set(keyXOffset, x)
 			} else {
-				return newOptionArgumentError("Invalid %s: %s", keyXOffset, args[1])
+				return newInvalidArgumentError(keyXOffset, args[1])
 			}
 		}
 
 		if nArgs > 2 {
-			if y, err := strconv.ParseFloat(args[2], 64); err == nil && isGravityOffcetValid(gType, y) {
+			if y, err := strconv.ParseFloat(args[2], 64); err == nil && isGravityOffsetValid(gType, y) {
 				o.Set(keyYOffset, y)
 			} else {
-				return newOptionArgumentError("Invalid %s: %s", keyYOffset, args[2])
+				return newInvalidArgumentError(keyYOffset, args[2])
 			}
 		}
 	}
@@ -235,7 +307,7 @@ func parseExtend(o *Options, key string, args []string) error {
 	}
 
 	if len(args) > 1 {
-		return parseGravity(o, key+keys.SuffixGravity, args[1:], extendGravityTypes)
+		return parseGravity(o, key+keys.SuffixGravity, extendGravityTypes, args[1:]...)
 	}
 
 	return nil