master
go 155 lines 3.2 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package selectorcore
4
5 import (
6 "fmt"
7 "regexp"
8 "strings"
9
10 "github.com/netdata/netdata/go/plugins/pkg/matcher"
11 )
12
13 var (
14 reLV = regexp.MustCompile(`^(?P<label_name>[a-zA-Z0-9_]+)(?P<op>=~|!~|=\*|!\*|=|!=)"(?P<pattern>.+)"$`)
15 )
16
17 // Parse parses one selector expression.
18 func Parse(expr string) (Selector, error) {
19 terms, err := splitSelectorTerms(unsugarExpr(expr))
20 if err != nil {
21 return nil, err
22 }
23
24 srs := make([]Selector, 0, len(terms))
25 for _, term := range terms {
26 sr, err := parseSelector(term)
27 if err != nil {
28 return nil, err
29 }
30 srs = append(srs, sr)
31 }
32
33 switch len(srs) {
34 case 0:
35 return nil, nil
36 case 1:
37 return srs[0], nil
38 default:
39 return And(srs[0], srs[1], srs[2:]...), nil
40 }
41 }
42
43 func parseSelector(line string) (Selector, error) {
44 sub := reLV.FindStringSubmatch(strings.TrimSpace(line))
45 if sub == nil {
46 return nil, fmt.Errorf("invalid selector syntax: '%s'", line)
47 }
48
49 name, op, pattern := sub[1], sub[2], strings.Trim(sub[3], "\"")
50
51 var m matcher.Matcher
52 var err error
53
54 switch op {
55 case OpEqual, OpNegEqual:
56 m, err = matcher.NewStringMatcher(pattern, true, true)
57 case OpRegexp, OpNegRegexp:
58 m, err = matcher.NewRegExpMatcher(pattern)
59 case OpSimplePatterns, OpNegSimplePatterns:
60 m, err = matcher.NewSimplePatternsMatcher(pattern)
61 default:
62 err = fmt.Errorf("unknown matching operator: %s", op)
63 }
64 if err != nil {
65 return nil, err
66 }
67
68 sr := labelSelector{name: name, m: m}
69 if strings.HasPrefix(op, "!") {
70 return Not(sr), nil
71 }
72 return sr, nil
73 }
74
75 func unsugarExpr(expr string) string {
76 // name => __name__=*"name"
77 // name{label="value"} => __name__=*"name",label="value"
78 // {label="value"} => label="value"
79 expr = strings.TrimSpace(expr)
80 if expr == "" {
81 return ""
82 }
83
84 switch idx := strings.IndexByte(expr, '{'); {
85 case idx == -1:
86 expr = fmt.Sprintf(`%s%s"%s"`,
87 MetricNameLabel,
88 OpSimplePatterns,
89 strings.TrimSpace(expr),
90 )
91 case idx == 0:
92 expr = strings.Trim(expr, "{}")
93 default:
94 expr = fmt.Sprintf(`%s%s"%s",%s`,
95 MetricNameLabel,
96 OpSimplePatterns,
97 strings.TrimSpace(expr[:idx]),
98 strings.Trim(expr[idx:], "{}"),
99 )
100 }
101 return expr
102 }
103
104 func splitSelectorTerms(expr string) ([]string, error) {
105 expr = strings.TrimSpace(expr)
106 if expr == "" {
107 return nil, nil
108 }
109
110 var (
111 terms []string
112 buf strings.Builder
113 inQuotes bool
114 escapePending bool
115 seenComma bool
116 )
117
118 for _, r := range expr {
119 switch {
120 case escapePending:
121 escapePending = false
122 buf.WriteRune(r)
123 case r == '\\':
124 escapePending = true
125 buf.WriteRune(r)
126 case r == '"':
127 inQuotes = !inQuotes
128 buf.WriteRune(r)
129 case r == ',' && !inQuotes:
130 seenComma = true
131 part := strings.TrimSpace(buf.String())
132 if part == "" {
133 return nil, fmt.Errorf("invalid selector syntax: empty matcher around comma")
134 }
135 terms = append(terms, part)
136 buf.Reset()
137 default:
138 buf.WriteRune(r)
139 }
140 }
141
142 if inQuotes {
143 return nil, fmt.Errorf("invalid selector syntax: unterminated quote")
144 }
145
146 last := strings.TrimSpace(buf.String())
147 if last == "" {
148 if seenComma {
149 return nil, fmt.Errorf("invalid selector syntax: trailing comma")
150 }
151 return nil, nil
152 }
153 terms = append(terms, last)
154 return terms, nil
155 }