alertmanager/pkg/labels/parse.go
beorn7 e87985a9a8 Add doc comments to labels.ParseMatcher and labels.ParseMatchers
The doc comments do not describe the current (arguably buggy) state,
but the desired state, as it will be implemented in future commits.

Signed-off-by: beorn7 <beorn@grafana.com>
2020-12-23 20:47:35 +01:00

129 lines
4.0 KiB
Go

// Copyright 2018 Prometheus Team
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package labels
import (
"regexp"
"strings"
"github.com/pkg/errors"
)
var (
re = regexp.MustCompile(`(?:\s?)(\w+)(=|=~|!=|!~)(?:\"([^"=~!]+)\"|([^"=~!]+)|\"\")`)
typeMap = map[string]MatchType{
"=": MatchEqual,
"!=": MatchNotEqual,
"=~": MatchRegexp,
"!~": MatchNotRegexp,
}
)
// ParseMatchers parses a comma-separated list of Matchers. A leading '{' and/or
// a trailing '}' is optional and will be trimmed before further
// parsing. Individual Matchers are separated by commas outside of quoted parts
// of the input string. Those commas may be followed by whitespace. Parts of the
// string inside unescaped double quotes ('"…"') are considered quoted (and
// commas don't act as separators there). If double quotes are escaped with a
// single backslash ('\"'), they are ignored for the purpose of identifying
// quoted parts of the input string. If the input string, after trimming the
// optional trailing '}', ends with a comma, followed by optional whitespace,
// this comma and whitespace will be trimmed.
//
// Examples for valid input strings:
// {foo = "bar", dings != "bums", }
// foo=bar,dings!=bums
// {quote="She said: \"Hi, ladies! That's gender-neutral…\""}
// statuscode=~"5.."
//
// See ParseMatcher for details how an individual Matcher is parsed.
func ParseMatchers(s string) ([]*Matcher, error) {
matchers := []*Matcher{}
s = strings.TrimPrefix(s, "{")
s = strings.TrimSuffix(s, "}")
var insideQuotes bool
var token string
var tokens []string
for _, r := range s {
if !insideQuotes && r == ',' {
tokens = append(tokens, token)
token = ""
continue
}
token += string(r)
if r == '"' {
insideQuotes = !insideQuotes
}
}
if token != "" {
tokens = append(tokens, token)
}
for _, token := range tokens {
m, err := ParseMatcher(token)
if err != nil {
return nil, err
}
matchers = append(matchers, m)
}
return matchers, nil
}
// ParseMatcher parses a matcher with a syntax inspired by PromQL and
// OpenMetrics. This syntax is convenient to describe filters and selectors in
// UIs and config files. To support the interactive nature of the use cases, the
// parser is in various aspects fairly tolerant.
//
// The syntax of a matcher consists of three tokens: (1) A valid Prometheus
// label name. (2) One of '=', '!=', '=~', or '!~', with the same meaning as
// known from PromQL selectors. (3) A UTF-8 string, which may be enclosed in
// double quotes. Before or after each token, there may be any amount of
// whitespace, which will be discarded. The 3rd token may be the empty
// string. Within the 3rd token, OpenMetrics escaping rules apply: '\"' for a
// double-quote, '\n' for a line feed, '\\' for a literal backslash. Unescaped
// '"' must not occur inside the 3rd token (only as the 1st or last
// character). However, literal line feed characters are tolerated, as are
// single '\' characters not followed by '\', 'n', or '"'. They act as a literal
// backslash in that case.
func ParseMatcher(s string) (*Matcher, error) {
var (
name, value string
matchType MatchType
)
ms := re.FindStringSubmatch(s)
if len(ms) < 4 {
return nil, errors.Errorf("bad matcher format: %s", s)
}
name = ms[1]
if name == "" {
return nil, errors.New("failed to parse label name")
}
matchType, found := typeMap[ms[2]]
if !found {
return nil, errors.New("failed to find match operator")
}
if ms[3] != "" {
value = ms[3]
} else {
value = ms[4]
}
return NewMatcher(matchType, name, value)
}