Skip to content

Commit 2a56d19

Browse files
authored
Merge pull request #66 from Jannes-Dailidow/main
FIX & RENAME: tag matcher parser
2 parents 39d0a14 + 84adabb commit 2a56d19

File tree

2 files changed

+47
-54
lines changed

2 files changed

+47
-54
lines changed

internal/db/bun_adapter.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ func getMultipleAccountsBun(ctx context.Context, bdb *bun.DB, opts ...func(*bun.
215215
}
216216

217217
func GetAccountsByTagBun(ctx context.Context, bdb *bun.DB, tag string) ([]model.Account, error) {
218-
tag_qb, err := tags.GetTagQueryBuilder(tag)
218+
tag_qb, err := tags.QueryBuilderFromTagMatcher(tag)
219219
if err != nil {
220220
return nil, err
221221
}

internal/db/tags/tags.go

Lines changed: 46 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ import (
1010
)
1111

1212
const 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
2020
func 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

181171
func 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

Comments
 (0)