@@ -4,7 +4,6 @@ package cel2sql
44import (
55 "context"
66 "encoding/hex"
7- "errors"
87 "fmt"
98 "log/slog"
109 "math"
@@ -330,7 +329,7 @@ func (con *converter) checkContext() error {
330329 return nil
331330 }
332331 if err := con .ctx .Err (); err != nil {
333- return fmt .Errorf ("conversion cancelled : %w" , err )
332+ return fmt .Errorf ("%w : %w" , ErrContextCanceled , err )
334333 }
335334 return nil
336335}
@@ -343,7 +342,7 @@ func (con *converter) visit(expr *exprpb.Expr) error {
343342 // Check depth limit before context check (fail fast)
344343 // Allow depths up to and including maxDepth
345344 if con .depth > con .maxDepth {
346- return fmt .Errorf ("expression exceeds maximum recursion depth of %d" , con .maxDepth )
345+ return fmt .Errorf ("%w: depth %d exceeds limit of %d" , ErrMaxDepthExceeded , con . depth , con .maxDepth )
347346 }
348347
349348 // Check for context cancellation at the main recursion entry point
@@ -353,7 +352,7 @@ func (con *converter) visit(expr *exprpb.Expr) error {
353352
354353 // Check SQL output length limit to prevent resource exhaustion (CWE-400)
355354 if con .str .Len () > con .maxOutputLen {
356- return fmt .Errorf ("generated SQL exceeds maximum output length of %d" , con .maxOutputLen )
355+ return fmt .Errorf ("%w: %d bytes exceeds limit of %d" , ErrMaxOutputLengthExceeded , con . str . Len () , con .maxOutputLen )
357356 }
358357
359358 switch expr .ExprKind .(type ) {
@@ -706,7 +705,7 @@ func (con *converter) callStartsWith(target *exprpb.Expr, args []*exprpb.Expr) e
706705 // or for more robust handling: LEFT(string, LENGTH(prefix)) = prefix
707706
708707 if target == nil || len (args ) == 0 {
709- return errors . New ( " startsWith function requires both string and prefix arguments" )
708+ return fmt . Errorf ( "%w: startsWith function requires both string and prefix arguments", ErrInvalidArguments )
710709 }
711710
712711 // Visit the string expression
@@ -723,7 +722,7 @@ func (con *converter) callStartsWith(target *exprpb.Expr, args []*exprpb.Expr) e
723722 prefix := constExpr .GetStringValue ()
724723 // Reject patterns containing null bytes
725724 if strings .Contains (prefix , "\x00 " ) {
726- return errors . New ( " LIKE patterns cannot contain null bytes" )
725+ return fmt . Errorf ( "%w: LIKE patterns cannot contain null bytes", ErrInvalidArguments )
727726 }
728727 // Escape special LIKE characters: %, _, \
729728 escaped := escapeLikePattern (prefix )
@@ -747,7 +746,7 @@ func (con *converter) callEndsWith(target *exprpb.Expr, args []*exprpb.Expr) err
747746 // or for more robust handling: RIGHT(string, LENGTH(suffix)) = suffix
748747
749748 if target == nil || len (args ) == 0 {
750- return errors . New ( " endsWith function requires both string and suffix arguments" )
749+ return fmt . Errorf ( "%w: endsWith function requires both string and suffix arguments", ErrInvalidArguments )
751750 }
752751
753752 // Visit the string expression
@@ -764,7 +763,7 @@ func (con *converter) callEndsWith(target *exprpb.Expr, args []*exprpb.Expr) err
764763 suffix := constExpr .GetStringValue ()
765764 // Reject patterns containing null bytes
766765 if strings .Contains (suffix , "\x00 " ) {
767- return errors . New ( " LIKE patterns cannot contain null bytes" )
766+ return fmt . Errorf ( "%w: LIKE patterns cannot contain null bytes", ErrInvalidArguments )
768767 }
769768 // Escape special LIKE characters: %, _, \
770769 escaped := escapeLikePattern (suffix )
@@ -837,7 +836,7 @@ func (con *converter) callMatches(target *exprpb.Expr, args []*exprpb.Expr) erro
837836 }
838837
839838 if stringExpr == nil || patternExpr == nil {
840- return errors . New ( " matches function requires both string and pattern arguments" )
839+ return fmt . Errorf ( "%w: matches function requires both string and pattern arguments", ErrInvalidArguments )
841840 }
842841
843842 // Visit the string expression
@@ -851,13 +850,13 @@ func (con *converter) callMatches(target *exprpb.Expr, args []*exprpb.Expr) erro
851850 re2Pattern := constExpr .GetStringValue ()
852851 // Reject patterns containing null bytes
853852 if strings .Contains (re2Pattern , "\x00 " ) {
854- return errors . New ( " regex patterns cannot contain null bytes" )
853+ return fmt . Errorf ( "%w: regex patterns cannot contain null bytes", ErrInvalidRegexPattern )
855854 }
856855
857856 // Convert RE2 to POSIX with security validation
858857 posixPattern , caseInsensitive , err := convertRE2ToPOSIX (re2Pattern )
859858 if err != nil {
860- return fmt .Errorf ("invalid regex pattern : %w" , err )
859+ return fmt .Errorf ("%w : %w" , ErrInvalidRegexPattern , err )
861860 }
862861
863862 con .logger .LogAttrs (context .Background (), slog .LevelDebug ,
@@ -1041,10 +1040,10 @@ func (con *converter) visitCallListIndex(expr *exprpb.Expr) error {
10411040 if constExpr := index .GetConstExpr (); constExpr != nil {
10421041 idx := constExpr .GetInt64Value ()
10431042 if idx == math .MaxInt64 {
1044- return errors . New ( " array index overflow: cannot convert math.MaxInt64 to 1-based indexing" )
1043+ return fmt . Errorf ( "%w: array index overflow, cannot convert math.MaxInt64 to 1-based indexing", ErrInvalidArguments )
10451044 }
10461045 if idx < 0 {
1047- return fmt .Errorf ("invalid negative array index: %d" , idx )
1046+ return fmt .Errorf ("%w: negative array index %d is not supported" , ErrInvalidArguments , idx )
10481047 }
10491048 con .str .WriteString (strconv .FormatInt (idx + 1 , 10 ))
10501049 } else {
@@ -1081,8 +1080,8 @@ func (con *converter) visitComprehension(expr *exprpb.Expr) error {
10811080
10821081 // Check comprehension depth limit before context check (fail fast)
10831082 if con .comprehensionDepth > maxComprehensionDepth {
1084- return fmt .Errorf ("comprehension nesting depth %d exceeds maximum of %d" ,
1085- con .comprehensionDepth , maxComprehensionDepth )
1083+ return fmt .Errorf ("%w: depth %d exceeds limit of %d" ,
1084+ ErrMaxComprehensionDepthExceeded , con .comprehensionDepth , maxComprehensionDepth )
10861085 }
10871086
10881087 // Check for context cancellation before processing comprehensions (potentially expensive)
@@ -1092,7 +1091,7 @@ func (con *converter) visitComprehension(expr *exprpb.Expr) error {
10921091
10931092 info , err := con .identifyComprehension (expr )
10941093 if err != nil {
1095- return fmt .Errorf ("failed to identify comprehension: %w" , err )
1094+ return fmt .Errorf ("%w: failed to identify comprehension: %w" , ErrInvalidComprehension , err )
10961095 }
10971096
10981097 switch info .Type {
@@ -1468,14 +1467,14 @@ func (con *converter) visitTransformMapComprehension(_ *exprpb.Expr, _ *Comprehe
14681467 // Generate SQL for TRANSFORM_MAP comprehension: work with map entries
14691468 // This is complex for PostgreSQL - maps are typically represented as JSON or composite types
14701469 // For now, return an error indicating this needs special handling
1471- return errors . New ( " TRANSFORM_MAP comprehension requires map/JSON support: not yet implemented" )
1470+ return fmt . Errorf ( "%w: TRANSFORM_MAP comprehension requires map/JSON support ( not yet implemented)" , ErrInvalidComprehension )
14721471}
14731472
14741473func (con * converter ) visitTransformMapEntryComprehension (_ * exprpb.Expr , _ * ComprehensionInfo ) error {
14751474 // Generate SQL for TRANSFORM_MAP_ENTRY comprehension: work with map key-value pairs
14761475 // This is complex for PostgreSQL - maps are typically represented as JSON or composite types
14771476 // For now, return an error indicating this needs special handling
1478- return errors . New ( " TRANSFORM_MAP_ENTRY comprehension requires map/JSON support: not yet implemented" )
1477+ return fmt . Errorf ( "%w: TRANSFORM_MAP_ENTRY comprehension requires map/JSON support ( not yet implemented)" , ErrInvalidComprehension )
14791478}
14801479
14811480func (con * converter ) visitConst (expr * exprpb.Expr ) error {
@@ -1522,7 +1521,7 @@ func (con *converter) visitConst(expr *exprpb.Expr) error {
15221521 str := c .GetStringValue ()
15231522 // Reject strings containing null bytes
15241523 if strings .Contains (str , "\x00 " ) {
1525- return errors . New ( " string literals cannot contain null bytes" )
1524+ return fmt . Errorf ( "%w: string literals cannot contain null bytes", ErrInvalidArguments )
15261525 }
15271526
15281527 if con .parameterize {
@@ -1547,7 +1546,7 @@ func (con *converter) visitConst(expr *exprpb.Expr) error {
15471546 } else {
15481547 // Validate byte array length to prevent resource exhaustion (CWE-400)
15491548 if len (b ) > maxByteArrayLength {
1550- return fmt .Errorf ("byte array exceeds maximum length of %d bytes (got %d bytes) " , maxByteArrayLength , len (b ))
1549+ return fmt .Errorf ("%w: %d bytes exceeds limit of %d bytes" , ErrInvalidByteArrayLength , len (b ), maxByteArrayLength )
15511550 }
15521551 con .str .WriteString ("'\\ x" )
15531552 con .str .WriteString (hex .EncodeToString (b ))
@@ -1564,7 +1563,7 @@ func (con *converter) visitIdent(expr *exprpb.Expr) error {
15641563
15651564 // Validate identifier name for security (prevent SQL injection)
15661565 if err := validateFieldName (identName ); err != nil {
1567- return fmt .Errorf ("invalid identifier name : %w" , err )
1566+ return fmt .Errorf ("%w : %w" , ErrInvalidFieldName , err )
15681567 }
15691568
15701569 // Check if this identifier needs numeric casting for JSON comprehensions
@@ -1601,7 +1600,7 @@ func (con *converter) visitSelect(expr *exprpb.Expr) error {
16011600 // Validate field name for security (prevent SQL injection)
16021601 fieldName := sel .GetField ()
16031602 if err := validateFieldName (fieldName ); err != nil {
1604- return fmt .Errorf ("invalid field name in select expression : %w" , err )
1603+ return fmt .Errorf ("%w : %w" , ErrInvalidFieldName , err )
16051604 }
16061605
16071606 // Handle the case when the select expression was generated by the has() macro.
@@ -1966,7 +1965,7 @@ func isBinaryOrTernaryOperator(expr *exprpb.Expr) bool {
19661965func convertRE2ToPOSIX (re2Pattern string ) (string , bool , error ) {
19671966 // 1. Check pattern length to prevent processing extremely long patterns
19681967 if len (re2Pattern ) > maxRegexPatternLength {
1969- return "" , false , fmt .Errorf ("regex pattern exceeds maximum length of %d characters" , maxRegexPatternLength )
1968+ return "" , false , fmt .Errorf ("%w: pattern length %d exceeds limit of %d characters" , ErrInvalidRegexPattern , len ( re2Pattern ) , maxRegexPatternLength )
19701969 }
19711970
19721971 // 2. Extract case-insensitive flag if present
@@ -1979,27 +1978,27 @@ func convertRE2ToPOSIX(re2Pattern string) (string, bool, error) {
19791978 // 3. Detect unsupported RE2 features and return errors
19801979 // Lookahead assertions
19811980 if strings .Contains (re2Pattern , "(?=" ) || strings .Contains (re2Pattern , "(?!" ) {
1982- return "" , false , errors . New ( " lookahead assertions (?=...), (?!...) are not supported in PostgreSQL POSIX regex" )
1981+ return "" , false , fmt . Errorf ( "%w: lookahead assertions (?=...), (?!...) are not supported in PostgreSQL POSIX regex", ErrInvalidRegexPattern )
19831982 }
19841983 // Lookbehind assertions
19851984 if strings .Contains (re2Pattern , "(?<=" ) || strings .Contains (re2Pattern , "(?<!" ) {
1986- return "" , false , errors . New ( " lookbehind assertions (?<=...), (?<!...) are not supported in PostgreSQL POSIX regex" )
1985+ return "" , false , fmt . Errorf ( "%w: lookbehind assertions (?<=...), (?<!...) are not supported in PostgreSQL POSIX regex", ErrInvalidRegexPattern )
19871986 }
19881987 // Named capture groups
19891988 if strings .Contains (re2Pattern , "(?P<" ) {
1990- return "" , false , errors . New ( " named capture groups (?P<name>...) are not supported in PostgreSQL POSIX regex" )
1989+ return "" , false , fmt . Errorf ( "%w: named capture groups (?P<name>...) are not supported in PostgreSQL POSIX regex", ErrInvalidRegexPattern )
19911990 }
19921991 // Other inline flags (after we've already handled (?i))
19931992 if strings .Contains (re2Pattern , "(?m" ) || strings .Contains (re2Pattern , "(?s" ) || strings .Contains (re2Pattern , "(?-" ) {
1994- return "" , false , errors . New ( " inline flags other than (?i) are not supported in PostgreSQL POSIX regex" )
1993+ return "" , false , fmt . Errorf ( "%w: inline flags other than (?i) are not supported in PostgreSQL POSIX regex", ErrInvalidRegexPattern )
19951994 }
19961995
19971996 // 4. Detect catastrophic nested quantifiers that cause exponential backtracking
19981997 // Patterns like (a+)+, (a*)*, (x+x+)+, ((a)+b)+, etc. are extremely dangerous
19991998
20001999 // Check for doubled quantifiers
20012000 if matched , _ := regexp .MatchString (`[*+][*+]` , re2Pattern ); matched {
2002- return "" , false , errors . New ( " regex contains catastrophic nested quantifiers that could cause ReDoS" )
2001+ return "" , false , fmt . Errorf ( "%w: regex contains catastrophic nested quantifiers that could cause ReDoS", ErrInvalidRegexPattern )
20032002 }
20042003
20052004 // Check for groups that contain quantifiers and are themselves quantified
@@ -2030,7 +2029,7 @@ func convertRE2ToPOSIX(re2Pattern string) (string, bool, error) {
20302029 if nextChar == '*' || nextChar == '+' || nextChar == '?' || nextChar == '{' {
20312030 // This group is quantified. Check if it contains quantifiers
20322031 if len (groupHasQuantifier ) > 0 && groupHasQuantifier [len (groupHasQuantifier )- 1 ] {
2033- return "" , false , errors . New ( " regex contains catastrophic nested quantifiers that could cause ReDoS" )
2032+ return "" , false , fmt . Errorf ( "%w: regex contains catastrophic nested quantifiers that could cause ReDoS", ErrInvalidRegexPattern )
20342033 }
20352034 }
20362035 }
@@ -2061,15 +2060,15 @@ func convertRE2ToPOSIX(re2Pattern string) (string, bool, error) {
20612060 // 5. Count and limit capture groups to prevent memory exhaustion
20622061 groupCount := strings .Count (re2Pattern , "(" ) - strings .Count (re2Pattern , `\(` )
20632062 if groupCount > maxRegexGroups {
2064- return "" , false , fmt .Errorf ("regex contains %d capture groups, exceeds maximum of %d" , groupCount , maxRegexGroups )
2063+ return "" , false , fmt .Errorf ("%w: regex contains %d capture groups, exceeds limit of %d" , ErrInvalidRegexPattern , groupCount , maxRegexGroups )
20652064 }
20662065
20672066 // 6. Detect exponential alternation patterns like (a|a)*b or (a|ab)*
20682067 alternationPattern := regexp .MustCompile (`\([^)]*\|[^)]*\)[*+]` )
20692068 if alternationPattern .MatchString (re2Pattern ) {
20702069 // Check if alternation has overlapping branches (more dangerous)
20712070 // This is a simple heuristic - full analysis would be more complex
2072- return "" , false , errors . New ( " regex contains quantified alternation that could cause ReDoS" )
2071+ return "" , false , fmt . Errorf ( "%w: regex contains quantified alternation that could cause ReDoS", ErrInvalidRegexPattern )
20732072 }
20742073
20752074 // 7. Check nesting depth to prevent deeply nested patterns
@@ -2086,7 +2085,7 @@ func convertRE2ToPOSIX(re2Pattern string) (string, bool, error) {
20862085 }
20872086 }
20882087 if maxDepth > maxRegexNestingDepth {
2089- return "" , false , fmt .Errorf ("regex nesting depth %d exceeds maximum of %d" , maxDepth , maxRegexNestingDepth )
2088+ return "" , false , fmt .Errorf ("%w: nesting depth %d exceeds limit of %d" , ErrInvalidRegexPattern , maxDepth , maxRegexNestingDepth )
20902089 }
20912090
20922091 // Passed all security checks - proceed with conversion
0 commit comments