@@ -10,11 +10,11 @@ import (
1010)
1111
1212const tagDelimiterChar string = "|"
13- const tagPatternExp string = `^[a-zA-Z0-9_\-+*/.:~=]+$`
14- const tagEscapeChar string = "!"
15- const tagEscapedChars string = `%_[]^-{}`
13+ const tagMatcherPatternExp string = `^[a-zA-Z0-9_\-+*/.:~=]+$`
14+ const sqlEscapeChar string = "!"
15+ const sqlEscapedChars string = `%_[]^-{}`
1616
17- var tagPattern = regexp .MustCompile (tagPatternExp )
17+ var tagMatcherPattern = regexp .MustCompile (tagMatcherPatternExp )
1818
1919// TODO vendor out to seperate package
2020func reducex [T any , S ~ []T , U any ](s S , f func (T , U ) (U , error )) (U , error ) {
@@ -30,41 +30,25 @@ func reducex[T any, S ~[]T, U any](s S, f func(T, U) (U, error)) (U, error) {
3030 return result , nil
3131}
3232
33- func parseTag (expr string , qb bun.QueryBuilder , mode bool , negate bool ) (bun.QueryBuilder , error ) {
33+ func parseTagMatcher (expr string , qb bun.QueryBuilder , mode bool , negate bool ) (bun.QueryBuilder , error ) {
3434 var err error
3535
3636 expr = strings .TrimSpace (expr )
3737
3838 // and
3939 if exprs := splitOnTopLevelChar (expr , '&' ); len (exprs ) > 1 {
40- // TODO test
40+ // TODO test & comment
4141 return reducex (exprs , func (expr string , qb bun.QueryBuilder ) (bun.QueryBuilder , error ) {
42- return parseTag (expr , qb , true != negate , negate )
42+ return parseTagMatcher (expr , qb , true != negate , negate )
4343 })
44-
45- // for _, expr = range exprs {
46- // qb, err = parseTag(expr, qb, true != negate, negate)
47- // if err != nil {
48- // return nil, err
49- // }
50- // }
51- // return qb, nil
5244 }
5345
5446 // or
5547 if exprs := splitOnTopLevelChar (expr , '|' ); len (exprs ) > 1 {
56- // TODO test
48+ // TODO test & comment
5749 return reducex (exprs , func (expr string , qb bun.QueryBuilder ) (bun.QueryBuilder , error ) {
58- return parseTag (expr , qb , false != negate , negate )
50+ return parseTagMatcher (expr , qb , false != negate , negate )
5951 })
60-
61- // for _, expr = range exprs {
62- // qb, err = parseTag(expr, qb, false != negate, negate)
63- // if err != nil {
64- // return nil, err
65- // }
66- // }
67- // return qb, nil
6852 }
6953
7054 // negation
@@ -74,13 +58,14 @@ func parseTag(expr string, qb bun.QueryBuilder, mode bool, negate bool) (bun.Que
7458
7559 // braces
7660 if strings .HasPrefix (expr , "(" ) && strings .HasSuffix (expr , ")" ) {
77- expr = expr [1 : len (expr )- 1 ] // removes braces
78-
61+ // removes braces
62+ expr = expr [1 : len (expr )- 1 ]
63+ // get WhereGroup prefix
7964 operator := map [bool ]string {
8065 true : " AND " ,
8166 false : " OR " ,
8267 }[mode ]
83-
68+ // flip negate flag for braces parsing, when braces are negated
8469 if negated {
8570 // return nil, fmt.Errorf("negating braces is unsupported: %s", expr)
8671 // Does not work because bun is a *****....
@@ -90,12 +75,12 @@ func parseTag(expr string, qb bun.QueryBuilder, mode bool, negate bool) (bun.Que
9075 // well, i think i got an idea ^^
9176 negate = ! negate
9277 }
93-
78+ // apply WhereGroup to query builder
9479 qb = qb .WhereGroup (operator , func (qb bun.QueryBuilder ) bun.QueryBuilder {
95- qb , err = parseTag (expr , qb , true != negate , negate )
80+ qb , err = parseTagMatcher (expr , qb , true != negate , negate )
9681 return qb
9782 })
98-
83+ // handle error from WhereGroup callback using global err variable
9984 if err != nil {
10085 return nil , err
10186 }
@@ -104,23 +89,25 @@ func parseTag(expr string, qb bun.QueryBuilder, mode bool, negate bool) (bun.Que
10489
10590 // raw tag value
10691 {
107- if ! tagPattern .MatchString (expr ) {
92+ // validate against tagPattern
93+ if ! tagMatcherPattern .MatchString (expr ) {
10894 return nil , fmt .Errorf ("invalid tag: %s" , expr )
10995 }
110-
11196 // escape special chars just to be sure
112- for _ , c := range tagEscapedChars {
113- expr = strings .ReplaceAll (expr , string (c ), tagEscapeChar + string (c ))
97+ for _ , c := range sqlEscapedChars {
98+ expr = strings .ReplaceAll (expr , string (c ), sqlEscapeChar + string (c ))
11499 }
115100 // enable wildcards
116101 expr = strings .ReplaceAll (expr , "**" , "%" )
117102 expr = strings .ReplaceAll (expr , "*" , "_" )
118-
103+ // add delimiters
104+ expr = JoinTags ([]string {expr })
105+ // construct query
119106 query := map [bool ]string {
120- true : "tag NOT LIKE ? ESCAPE '" + tagEscapeChar + "'" ,
121- false : "tag LIKE ? ESCAPE '" + tagEscapeChar + "'" ,
107+ true : "tag NOT LIKE ? ESCAPE '" + sqlEscapeChar + "'" ,
108+ false : "tag LIKE ? ESCAPE '" + sqlEscapeChar + "'" ,
122109 }[negated != negate ]
123-
110+ // apply to query builder
124111 if mode {
125112 return qb .Where (query , expr ), nil
126113 } else {
@@ -148,38 +135,44 @@ func splitOnTopLevelChar(expr string, op rune) []string {
148135 }
149136 }
150137
151- result = append (result , expr [start :])
152- return result
138+ return append (result , expr [start :])
153139}
154140
155- func ValidateTag (tag string ) error {
156- sq := & bun.SelectQuery {}
157- _ , err := parseTag (tag , sq .QueryBuilder (), true , false )
141+ func ValidateTagMatcher (tag_matcher string ) error {
142+ // create mock QueryBuilder (fine for validation, but panics when used to render sql as it has no underlying formatter!)
143+ qb := (& bun.SelectQuery {}).QueryBuilder ()
144+ _ , err := parseTagMatcher (tag_matcher , qb , true , false )
158145 return err
159146}
160147
161- func GetTagQueryBuilder (tag string ) (func (bun.QueryBuilder ) bun.QueryBuilder , error ) {
162- if err := ValidateTag (tag ); err != nil {
148+ func QueryBuilderFromTagMatcher (tag_matcher string ) (func (bun.QueryBuilder ) bun.QueryBuilder , error ) {
149+ // validate before returning QueryBuilder, because errors can't be returned from the QueryBuilder callback
150+ if err := ValidateTagMatcher (tag_matcher ); err != nil {
163151 return nil , err
164152 }
153+ // return QueryBuilder with safe callback
165154 return func (qb bun.QueryBuilder ) bun.QueryBuilder {
166- qb , _ = parseTag ( tag , qb , true , false )
155+ qb , _ = parseTagMatcher ( tag_matcher , qb , true , false )
167156 return qb
168157 }, nil
169158}
170159
171- func SplitTags (tag string ) ([]string , error ) {
172- tag , exists_prefix := strings .CutPrefix (tag , tagDelimiterChar )
173- tag , exists_suffix := strings .CutSuffix (tag , tagDelimiterChar )
160+ func SplitTags (tags string ) ([]string , error ) {
161+ // validate and strip prefix & suffix
162+ tags , exists_prefix := strings .CutPrefix (tags , tagDelimiterChar )
163+ tags , exists_suffix := strings .CutSuffix (tags , tagDelimiterChar )
174164 if ! exists_prefix || ! exists_suffix {
175165 return nil , errors .New ("Prefix or suffix is missing" )
176166 }
177-
178- return strings .Split (tag , tagDelimiterChar ), nil
167+ // split and return tags
168+ return strings .Split (tags , tagDelimiterChar ), nil
179169}
180170
181171func SplitTagsSafe (tag string ) []string {
182- tags , _ := SplitTags (tag )
172+ tags , err := SplitTags (tag )
173+ if err != nil {
174+ return []string {}
175+ }
183176 return tags
184177}
185178
0 commit comments