package validators import ( "fmt" "github.com/go-playground/validator/v10" "reflect" "regexp" "strconv" "strings" "time" ) var Validate *validator.Validate func init() { Validate = validator.New() Validate.RegisterTagNameFunc(func(fld reflect.StructField) string { name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0] if name == "-" { return "" } return name }) } // ID format: 12-character hexadecimal string (from gen_random_bytes(6) encoded as hex) var validIDRegex = regexp.MustCompile(`^[0-9a-fA-F]{12}$`) // IsValidID checks if an ID is valid based on the database constraint (CHAR(12) hex string) // Valid IDs are exactly 12 hexadecimal characters (0-9, a-f, A-F) func IsValidID(id string) bool { if id == "" { return false } return validIDRegex.MatchString(id) } // Square checkout IDs are opaque strings (e.g. "08YceKh7B3ZqO") — NOT local // 12-hex DB IDs, so IsValidID must not gate them (it would 404 every real // checkout). Accept any non-empty ID matching Square's character set with a // sane length bound, and reject anything that could inject into the URL path. var squareCheckoutIDRegex = regexp.MustCompile(`^[A-Za-z0-9_\-]{8,64}$`) func IsValidSquareCheckoutID(id string) bool { if id == "" { return false } return squareCheckoutIDRegex.MatchString(id) } // ParseCursor splits a "createdAt|id" cursor string into its components. func ParseCursor(cursor string) (time.Time, string, error) { parts := strings.SplitN(cursor, "|", 2) if len(parts) != 2 { return time.Time{}, "", fmt.Errorf("invalid cursor format") } t, err := time.Parse(time.RFC3339, parts[0]) if err != nil { return time.Time{}, "", fmt.Errorf("invalid cursor created_at: %w", err) } return t, parts[1], nil } func ParseCursor3(cursor string) (int, time.Time, string, error) { parts := strings.SplitN(cursor, "|", 3) if len(parts) != 3 { return 0, time.Time{}, "", fmt.Errorf("invalid cursor format") } count, err := strconv.Atoi(parts[0]) if err != nil { return 0, time.Time{}, "", fmt.Errorf("invalid cursor completed_count: %w", err) } t, err := time.Parse(time.RFC3339, parts[1]) if err != nil { return 0, time.Time{}, "", fmt.Errorf("invalid cursor created_at: %w", err) } return count, t, parts[2], nil }