Skip to content

Commit c79a891

Browse files
authored
Add ValidationScheme methods IsValidMetricName and IsValidLabelName (#806)
* Add ValidationScheme methods IsValidMetricName and IsValidLabelName --------- Signed-off-by: Arve Knudsen <[email protected]>
1 parent 0e1982f commit c79a891

File tree

7 files changed

+133
-72
lines changed

7 files changed

+133
-72
lines changed

expfmt/decode.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ func (d *protoDecoder) Decode(v *dto.MetricFamily) error {
9393
if err := opts.UnmarshalFrom(d.r, v); err != nil {
9494
return err
9595
}
96+
//nolint:staticcheck // model.IsValidMetricName is deprecated.
9697
if !model.IsValidMetricName(model.LabelValue(v.GetName())) {
9798
return fmt.Errorf("invalid metric name %q", v.GetName())
9899
}
@@ -107,6 +108,7 @@ func (d *protoDecoder) Decode(v *dto.MetricFamily) error {
107108
if !model.LabelValue(l.GetValue()).IsValid() {
108109
return fmt.Errorf("invalid label value %q", l.GetValue())
109110
}
111+
//nolint:staticcheck // model.LabelName.IsValid is deprecated.
110112
if !model.LabelName(l.GetName()).IsValid() {
111113
return fmt.Errorf("invalid label name %q", l.GetName())
112114
}

expfmt/openmetrics_create.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -477,7 +477,7 @@ func writeOpenMetricsNameAndLabelPairs(
477477
if name != "" {
478478
// If the name does not pass the legacy validity check, we must put the
479479
// metric name inside the braces, quoted.
480-
if !model.IsValidLegacyMetricName(name) {
480+
if !model.LegacyValidation.IsValidMetricName(name) {
481481
metricInsideBraces = true
482482
err := w.WriteByte(separator)
483483
written++

expfmt/text_create.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,7 @@ func writeNameAndLabelPairs(
354354
if name != "" {
355355
// If the name does not pass the legacy validity check, we must put the
356356
// metric name inside the braces.
357-
if !model.IsValidLegacyMetricName(name) {
357+
if !model.LegacyValidation.IsValidMetricName(name) {
358358
metricInsideBraces = true
359359
err := w.WriteByte(separator)
360360
written++
@@ -498,7 +498,7 @@ func writeInt(w enhancedWriter, i int64) (int, error) {
498498
// writeName writes a string as-is if it complies with the legacy naming
499499
// scheme, or escapes it in double quotes if not.
500500
func writeName(w enhancedWriter, name string) (int, error) {
501-
if model.IsValidLegacyMetricName(name) {
501+
if model.LegacyValidation.IsValidMetricName(name) {
502502
return w.WriteString(name)
503503
}
504504
var written int

model/labels.go

Lines changed: 8 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -106,34 +106,21 @@ type LabelName string
106106
// IsValid returns true iff the name matches the pattern of LabelNameRE when
107107
// NameValidationScheme is set to LegacyValidation, or valid UTF-8 if
108108
// NameValidationScheme is set to UTF8Validation.
109+
//
110+
// Deprecated: This method should not be used and may be removed in the future.
111+
// Use [ValidationScheme.IsValidLabelName] instead.
109112
func (ln LabelName) IsValid() bool {
110-
if len(ln) == 0 {
111-
return false
112-
}
113-
switch NameValidationScheme {
114-
case LegacyValidation:
115-
return ln.IsValidLegacy()
116-
case UTF8Validation:
117-
return utf8.ValidString(string(ln))
118-
default:
119-
panic(fmt.Sprintf("Invalid name validation scheme requested: %d", NameValidationScheme))
120-
}
113+
return NameValidationScheme.IsValidLabelName(string(ln))
121114
}
122115

123116
// IsValidLegacy returns true iff name matches the pattern of LabelNameRE for
124117
// legacy names. It does not use LabelNameRE for the check but a much faster
125118
// hardcoded implementation.
119+
//
120+
// Deprecated: This method should not be used and may be removed in the future.
121+
// Use [LegacyValidation.IsValidLabelName] instead.
126122
func (ln LabelName) IsValidLegacy() bool {
127-
if len(ln) == 0 {
128-
return false
129-
}
130-
for i, b := range ln {
131-
// TODO: Apply De Morgan's law. Make sure there are tests for this.
132-
if !((b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || b == '_' || (b >= '0' && b <= '9' && i > 0)) { //nolint:staticcheck
133-
return false
134-
}
135-
}
136-
return true
123+
return LegacyValidation.IsValidLabelName(string(ln))
137124
}
138125

139126
// UnmarshalYAML implements the yaml.Unmarshaler interface.

model/labels_test.go

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
package model
1515

1616
import (
17+
"fmt"
1718
"sort"
1819
"testing"
1920
)
@@ -90,9 +91,9 @@ func BenchmarkLabelValues(b *testing.B) {
9091
}
9192
}
9293

93-
func TestLabelNameIsValid(t *testing.T) {
94+
func TestValidationScheme_IsLabelNameValid(t *testing.T) {
9495
scenarios := []struct {
95-
ln LabelName
96+
ln string
9697
legacyValid bool
9798
utf8Valid bool
9899
}{
@@ -141,20 +142,39 @@ func TestLabelNameIsValid(t *testing.T) {
141142
legacyValid: false,
142143
utf8Valid: false,
143144
},
145+
{
146+
ln: "",
147+
legacyValid: false,
148+
utf8Valid: false,
149+
},
144150
}
145-
146151
for _, s := range scenarios {
147-
NameValidationScheme = LegacyValidation
148-
if s.ln.IsValid() != s.legacyValid {
149-
t.Errorf("Expected %v for %q using legacy IsValid method", s.legacyValid, s.ln)
150-
}
151-
if LabelNameRE.MatchString(string(s.ln)) != s.legacyValid {
152-
t.Errorf("Expected %v for %q using legacy regexp match", s.legacyValid, s.ln)
153-
}
154-
NameValidationScheme = UTF8Validation
155-
if s.ln.IsValid() != s.utf8Valid {
156-
t.Errorf("Expected %v for %q using UTF-8 IsValid method", s.legacyValid, s.ln)
157-
}
152+
t.Run(fmt.Sprintf("%s,%t,%t", s.ln, s.legacyValid, s.utf8Valid), func(t *testing.T) {
153+
if LegacyValidation.IsValidLabelName(s.ln) != s.legacyValid {
154+
t.Errorf("Expected %v for %q using LegacyValidation.IsValidLabelName", s.legacyValid, s.ln)
155+
}
156+
if LabelNameRE.MatchString(s.ln) != s.legacyValid {
157+
t.Errorf("Expected %v for %q using legacy regexp match", s.legacyValid, s.ln)
158+
}
159+
if UTF8Validation.IsValidLabelName(s.ln) != s.utf8Valid {
160+
t.Errorf("Expected %v for %q using UTF8Validation.IsValidLabelName", s.utf8Valid, s.ln)
161+
}
162+
163+
// Test deprecated functions.
164+
origScheme := NameValidationScheme
165+
t.Cleanup(func() {
166+
NameValidationScheme = origScheme
167+
})
168+
NameValidationScheme = LegacyValidation
169+
labelName := LabelName(s.ln)
170+
if labelName.IsValid() != s.legacyValid {
171+
t.Errorf("Expected %v for %q using legacy IsValid method", s.legacyValid, s.ln)
172+
}
173+
NameValidationScheme = UTF8Validation
174+
if labelName.IsValid() != s.utf8Valid {
175+
t.Errorf("Expected %v for %q using UTF-8 IsValid method", s.utf8Valid, s.ln)
176+
}
177+
})
158178
}
159179
}
160180

model/metric.go

Lines changed: 55 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,53 @@ func (s *ValidationScheme) UnmarshalYAML(unmarshal func(any) error) error {
127127
return nil
128128
}
129129

130+
// IsValidMetricName returns whether metricName is valid according to s.
131+
func (s ValidationScheme) IsValidMetricName(metricName string) bool {
132+
switch s {
133+
case LegacyValidation:
134+
if len(metricName) == 0 {
135+
return false
136+
}
137+
for i, b := range metricName {
138+
if !isValidLegacyRune(b, i) {
139+
return false
140+
}
141+
}
142+
return true
143+
case UTF8Validation:
144+
if len(metricName) == 0 {
145+
return false
146+
}
147+
return utf8.ValidString(metricName)
148+
default:
149+
panic(fmt.Sprintf("Invalid name validation scheme requested: %s", s.String()))
150+
}
151+
}
152+
153+
// IsValidLabelName returns whether labelName is valid according to s.
154+
func (s ValidationScheme) IsValidLabelName(labelName string) bool {
155+
switch s {
156+
case LegacyValidation:
157+
if len(labelName) == 0 {
158+
return false
159+
}
160+
for i, b := range labelName {
161+
// TODO: Apply De Morgan's law. Make sure there are tests for this.
162+
if !((b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || b == '_' || (b >= '0' && b <= '9' && i > 0)) { //nolint:staticcheck
163+
return false
164+
}
165+
}
166+
return true
167+
case UTF8Validation:
168+
if len(labelName) == 0 {
169+
return false
170+
}
171+
return utf8.ValidString(labelName)
172+
default:
173+
panic(fmt.Sprintf("Invalid name validation scheme requested: %s", s))
174+
}
175+
}
176+
130177
type EscapingScheme int
131178

132179
const (
@@ -230,34 +277,22 @@ func (m Metric) FastFingerprint() Fingerprint {
230277
// IsValidMetricName returns true iff name matches the pattern of MetricNameRE
231278
// for legacy names, and iff it's valid UTF-8 if the UTF8Validation scheme is
232279
// selected.
280+
//
281+
// Deprecated: This function should not be used and might be removed in the future.
282+
// Use [ValidationScheme.IsValidMetricName] instead.
233283
func IsValidMetricName(n LabelValue) bool {
234-
switch NameValidationScheme {
235-
case LegacyValidation:
236-
return IsValidLegacyMetricName(string(n))
237-
case UTF8Validation:
238-
if len(n) == 0 {
239-
return false
240-
}
241-
return utf8.ValidString(string(n))
242-
default:
243-
panic(fmt.Sprintf("Invalid name validation scheme requested: %s", NameValidationScheme.String()))
244-
}
284+
return NameValidationScheme.IsValidMetricName(string(n))
245285
}
246286

247287
// IsValidLegacyMetricName is similar to IsValidMetricName but always uses the
248288
// legacy validation scheme regardless of the value of NameValidationScheme.
249289
// This function, however, does not use MetricNameRE for the check but a much
250290
// faster hardcoded implementation.
291+
//
292+
// Deprecated: This function should not be used and might be removed in the future.
293+
// Use [LegacyValidation.IsValidMetricName] instead.
251294
func IsValidLegacyMetricName(n string) bool {
252-
if len(n) == 0 {
253-
return false
254-
}
255-
for i, b := range n {
256-
if !isValidLegacyRune(b, i) {
257-
return false
258-
}
259-
}
260-
return true
295+
return LegacyValidation.IsValidMetricName(n)
261296
}
262297

263298
// EscapeMetricFamily escapes the given metric names and labels with the given

model/metric_test.go

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ package model
1515

1616
import (
1717
"errors"
18+
"fmt"
1819
"strings"
1920
"testing"
2021

@@ -202,9 +203,9 @@ func TestValidationScheme_UnmarshalYAML(t *testing.T) {
202203
}
203204
}
204205

205-
func TestMetricNameIsLegacyValid(t *testing.T) {
206+
func TestValidationScheme_IsMetricNameValid(t *testing.T) {
206207
scenarios := []struct {
207-
mn LabelValue
208+
mn string
208209
legacyValid bool
209210
utf8Valid bool
210211
}{
@@ -259,19 +260,35 @@ func TestMetricNameIsLegacyValid(t *testing.T) {
259260
utf8Valid: false,
260261
},
261262
}
262-
263263
for _, s := range scenarios {
264-
NameValidationScheme = LegacyValidation
265-
if IsValidMetricName(s.mn) != s.legacyValid {
266-
t.Errorf("Expected %v for %q using legacy IsValidMetricName method", s.legacyValid, s.mn)
267-
}
268-
if MetricNameRE.MatchString(string(s.mn)) != s.legacyValid {
269-
t.Errorf("Expected %v for %q using regexp matching", s.legacyValid, s.mn)
270-
}
271-
NameValidationScheme = UTF8Validation
272-
if IsValidMetricName(s.mn) != s.utf8Valid {
273-
t.Errorf("Expected %v for %q using utf-8 IsValidMetricName method", s.legacyValid, s.mn)
274-
}
264+
t.Run(fmt.Sprintf("%s,%t,%t", s.mn, s.legacyValid, s.utf8Valid), func(t *testing.T) {
265+
if LegacyValidation.IsValidMetricName(s.mn) != s.legacyValid {
266+
t.Errorf("Expected %v for %q using LegacyValidation.IsValidMetricName", s.legacyValid, s.mn)
267+
}
268+
if MetricNameRE.MatchString(string(s.mn)) != s.legacyValid {
269+
t.Errorf("Expected %v for %q using regexp matching", s.legacyValid, s.mn)
270+
}
271+
if UTF8Validation.IsValidMetricName(s.mn) != s.utf8Valid {
272+
t.Errorf("Expected %v for %q using UTF8Validation.IsValidMetricName", s.utf8Valid, s.mn)
273+
}
274+
275+
// Test deprecated functions.
276+
if IsValidLegacyMetricName(s.mn) != s.legacyValid {
277+
t.Errorf("Expected %v for %q using IsValidLegacyMetricNames", s.legacyValid, s.mn)
278+
}
279+
origScheme := NameValidationScheme
280+
t.Cleanup(func() {
281+
NameValidationScheme = origScheme
282+
})
283+
NameValidationScheme = LegacyValidation
284+
if IsValidMetricName(LabelValue(s.mn)) != s.legacyValid {
285+
t.Errorf("Expected %v for %q using legacy IsValidMetricName", s.legacyValid, s.mn)
286+
}
287+
NameValidationScheme = UTF8Validation
288+
if IsValidMetricName(LabelValue(s.mn)) != s.utf8Valid {
289+
t.Errorf("Expected %v for %q using utf-8 IsValidMetricName method", s.utf8Valid, s.mn)
290+
}
291+
})
275292
}
276293
}
277294

0 commit comments

Comments
 (0)