diff --git a/pkg/labels/matcher.go b/pkg/labels/matcher.go index a0f97413..308ed9d4 100644 --- a/pkg/labels/matcher.go +++ b/pkg/labels/matcher.go @@ -15,6 +15,7 @@ package labels import ( "bytes" + "encoding/json" "fmt" "regexp" "strings" @@ -91,6 +92,49 @@ func (m *Matcher) Matches(s string) bool { panic("labels.Matcher.Matches: invalid match type") } +type apiV1Matcher struct { + Name string `json:"name"` + Value string `json:"value"` + IsRegex bool `json:"isRegex"` + IsEqual bool `json:"isEqual"` +} + +// MarshalJSON retains backwards compatibility with types.Matcher for the v1 API. +func (m Matcher) MarshalJSON() ([]byte, error) { + return json.Marshal(apiV1Matcher{ + Name: m.Name, + Value: m.Value, + IsRegex: m.Type == MatchRegexp || m.Type == MatchNotRegexp, + IsEqual: m.Type == MatchRegexp || m.Type == MatchEqual, + }) +} + +func (m *Matcher) UnmarshalJSON(data []byte) error { + var v1m apiV1Matcher + if err := json.Unmarshal(data, &v1m); err != nil { + return err + } + + var t MatchType + switch { + case v1m.IsEqual && !v1m.IsRegex: + t = MatchEqual + case !v1m.IsEqual && !v1m.IsRegex: + t = MatchNotEqual + case v1m.IsEqual && v1m.IsRegex: + t = MatchRegexp + case !v1m.IsEqual && v1m.IsRegex: + t = MatchNotRegexp + } + + matcher, err := NewMatcher(t, v1m.Name, v1m.Value) + if err != nil { + return err + } + *m = *matcher + return nil +} + // openMetricsEscape is similar to the usual string escaping, but more // restricted. It merely replaces a new-line character with '\n', a double-quote // character with '\"', and a backslash with '\\', which is the escaping used by diff --git a/pkg/labels/matcher_test.go b/pkg/labels/matcher_test.go index 95e643c2..a645f9d6 100644 --- a/pkg/labels/matcher_test.go +++ b/pkg/labels/matcher_test.go @@ -13,7 +13,10 @@ package labels -import "testing" +import ( + "encoding/json" + "testing" +) func mustNewMatcher(t *testing.T, mType MatchType, value string) *Matcher { m, err := NewMatcher(mType, "", value) @@ -191,3 +194,64 @@ line`, } } } + +func TestMatcherJSON(t *testing.T) { + tests := []struct { + name string + op MatchType + value string + want string + }{ + { + name: `foo`, + op: MatchEqual, + value: `bar`, + want: `{"name":"foo","value":"bar","isRegex":false,"isEqual":true}`, + }, + { + name: `foo`, + op: MatchNotEqual, + value: `bar`, + want: `{"name":"foo","value":"bar","isRegex":false,"isEqual":false}`, + }, + { + name: `foo`, + op: MatchRegexp, + value: `bar`, + want: `{"name":"foo","value":"bar","isRegex":true,"isEqual":true}`, + }, + { + name: `foo`, + op: MatchNotRegexp, + value: `bar`, + want: `{"name":"foo","value":"bar","isRegex":true,"isEqual":false}`, + }, + } + + cmp := func(m1, m2 Matcher) bool { + return m1.Name == m2.Name && m1.Value == m2.Value && m1.Type == m2.Type + } + + for _, test := range tests { + m, err := NewMatcher(test.op, test.name, test.value) + if err != nil { + t.Fatal(err) + } + + b, err := json.Marshal(m) + if err != nil { + t.Fatal(err) + } + if got := string(b); got != test.want { + t.Errorf("Unexpected JSON representation of matcher:\nwant:\t%v\ngot:\t%v", test.want, got) + } + + var m2 Matcher + if err := json.Unmarshal(b, &m2); err != nil { + t.Fatal(err) + } + if !cmp(*m, m2) { + t.Errorf("Doing Marshal and Unmarshal seems to be losing data; before %#v, after %#v", m, m2) + } + } +}