Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions expfmt/decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ func (d *protoDecoder) Decode(v *dto.MetricFamily) error {
if err := opts.UnmarshalFrom(d.r, v); err != nil {
return err
}
//nolint:staticcheck // model.IsValidMetricName is deprecated.
if !model.IsValidMetricName(model.LabelValue(v.GetName())) {
return fmt.Errorf("invalid metric name %q", v.GetName())
}
Expand All @@ -107,6 +108,7 @@ func (d *protoDecoder) Decode(v *dto.MetricFamily) error {
if !model.LabelValue(l.GetValue()).IsValid() {
return fmt.Errorf("invalid label value %q", l.GetValue())
}
//nolint:staticcheck // model.LabelName.IsValid is deprecated.
if !model.LabelName(l.GetName()).IsValid() {
return fmt.Errorf("invalid label name %q", l.GetName())
}
Expand Down
2 changes: 1 addition & 1 deletion expfmt/openmetrics_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -477,7 +477,7 @@ func writeOpenMetricsNameAndLabelPairs(
if name != "" {
// If the name does not pass the legacy validity check, we must put the
// metric name inside the braces, quoted.
if !model.IsValidLegacyMetricName(name) {
if !model.LegacyValidation.IsValidMetricName(name) {
metricInsideBraces = true
err := w.WriteByte(separator)
written++
Expand Down
4 changes: 2 additions & 2 deletions expfmt/text_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,7 @@ func writeNameAndLabelPairs(
if name != "" {
// If the name does not pass the legacy validity check, we must put the
// metric name inside the braces.
if !model.IsValidLegacyMetricName(name) {
if !model.LegacyValidation.IsValidMetricName(name) {
metricInsideBraces = true
err := w.WriteByte(separator)
written++
Expand Down Expand Up @@ -498,7 +498,7 @@ func writeInt(w enhancedWriter, i int64) (int, error) {
// writeName writes a string as-is if it complies with the legacy naming
// scheme, or escapes it in double quotes if not.
func writeName(w enhancedWriter, name string) (int, error) {
if model.IsValidLegacyMetricName(name) {
if model.LegacyValidation.IsValidMetricName(name) {
return w.WriteString(name)
}
var written int
Expand Down
29 changes: 8 additions & 21 deletions model/labels.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,34 +106,21 @@ type LabelName string
// IsValid returns true iff the name matches the pattern of LabelNameRE when
// NameValidationScheme is set to LegacyValidation, or valid UTF-8 if
// NameValidationScheme is set to UTF8Validation.
//
// Deprecated: This method should not be used and may be removed in the future.
// Use [ValidationScheme.IsValidLabelName] instead.
func (ln LabelName) IsValid() bool {
if len(ln) == 0 {
return false
}
switch NameValidationScheme {
case LegacyValidation:
return ln.IsValidLegacy()
case UTF8Validation:
return utf8.ValidString(string(ln))
default:
panic(fmt.Sprintf("Invalid name validation scheme requested: %d", NameValidationScheme))
}
return NameValidationScheme.IsValidLabelName(string(ln))
}

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

// UnmarshalYAML implements the yaml.Unmarshaler interface.
Expand Down
48 changes: 34 additions & 14 deletions model/labels_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
package model

import (
"fmt"
"sort"
"testing"
)
Expand Down Expand Up @@ -90,9 +91,9 @@ func BenchmarkLabelValues(b *testing.B) {
}
}

func TestLabelNameIsValid(t *testing.T) {
func TestValidationScheme_IsLabelNameValid(t *testing.T) {
scenarios := []struct {
ln LabelName
ln string
legacyValid bool
utf8Valid bool
}{
Expand Down Expand Up @@ -141,20 +142,39 @@ func TestLabelNameIsValid(t *testing.T) {
legacyValid: false,
utf8Valid: false,
},
{
ln: "",
legacyValid: false,
utf8Valid: false,
},
}

for _, s := range scenarios {
NameValidationScheme = LegacyValidation
if s.ln.IsValid() != s.legacyValid {
t.Errorf("Expected %v for %q using legacy IsValid method", s.legacyValid, s.ln)
}
if LabelNameRE.MatchString(string(s.ln)) != s.legacyValid {
t.Errorf("Expected %v for %q using legacy regexp match", s.legacyValid, s.ln)
}
NameValidationScheme = UTF8Validation
if s.ln.IsValid() != s.utf8Valid {
t.Errorf("Expected %v for %q using UTF-8 IsValid method", s.legacyValid, s.ln)
}
t.Run(fmt.Sprintf("%s,%t,%t", s.ln, s.legacyValid, s.utf8Valid), func(t *testing.T) {
if LegacyValidation.IsValidLabelName(s.ln) != s.legacyValid {
t.Errorf("Expected %v for %q using LegacyValidation.IsValidLabelName", s.legacyValid, s.ln)
}
if LabelNameRE.MatchString(s.ln) != s.legacyValid {
t.Errorf("Expected %v for %q using legacy regexp match", s.legacyValid, s.ln)
}
if UTF8Validation.IsValidLabelName(s.ln) != s.utf8Valid {
t.Errorf("Expected %v for %q using UTF8Validation.IsValidLabelName", s.utf8Valid, s.ln)
}

// Test deprecated functions.
origScheme := NameValidationScheme
t.Cleanup(func() {
NameValidationScheme = origScheme
})
NameValidationScheme = LegacyValidation
labelName := LabelName(s.ln)
if labelName.IsValid() != s.legacyValid {
t.Errorf("Expected %v for %q using legacy IsValid method", s.legacyValid, s.ln)
}
NameValidationScheme = UTF8Validation
if labelName.IsValid() != s.utf8Valid {
t.Errorf("Expected %v for %q using UTF-8 IsValid method", s.utf8Valid, s.ln)
}
})
}
}

Expand Down
75 changes: 55 additions & 20 deletions model/metric.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,53 @@ func (s *ValidationScheme) UnmarshalYAML(unmarshal func(any) error) error {
return nil
}

// IsValidMetricName returns whether metricName is valid according to s.
func (s ValidationScheme) IsValidMetricName(metricName string) bool {
switch s {
case LegacyValidation:
if len(metricName) == 0 {
return false
}
for i, b := range metricName {
if !isValidLegacyRune(b, i) {
return false
}
}
return true
case UTF8Validation:
if len(metricName) == 0 {
return false
}
return utf8.ValidString(metricName)
default:
panic(fmt.Sprintf("Invalid name validation scheme requested: %s", s.String()))
}
}

// IsValidLabelName returns whether labelName is valid according to s.
func (s ValidationScheme) IsValidLabelName(labelName string) bool {
switch s {
case LegacyValidation:
if len(labelName) == 0 {
return false
}
for i, b := range labelName {
// TODO: Apply De Morgan's law. Make sure there are tests for this.
Comment thread
aknuds1 marked this conversation as resolved.
if !((b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || b == '_' || (b >= '0' && b <= '9' && i > 0)) { //nolint:staticcheck
return false
}
}
return true
case UTF8Validation:
if len(labelName) == 0 {
return false
}
return utf8.ValidString(labelName)
default:
panic(fmt.Sprintf("Invalid name validation scheme requested: %s", s))
}
}

type EscapingScheme int

const (
Expand Down Expand Up @@ -230,34 +277,22 @@ func (m Metric) FastFingerprint() Fingerprint {
// IsValidMetricName returns true iff name matches the pattern of MetricNameRE
// for legacy names, and iff it's valid UTF-8 if the UTF8Validation scheme is
// selected.
//
// Deprecated: This function should not be used and might be removed in the future.
// Use [ValidationScheme.IsValidMetricName] instead.
func IsValidMetricName(n LabelValue) bool {
switch NameValidationScheme {
case LegacyValidation:
return IsValidLegacyMetricName(string(n))
case UTF8Validation:
if len(n) == 0 {
return false
}
return utf8.ValidString(string(n))
default:
panic(fmt.Sprintf("Invalid name validation scheme requested: %s", NameValidationScheme.String()))
}
return NameValidationScheme.IsValidMetricName(string(n))
}

// IsValidLegacyMetricName is similar to IsValidMetricName but always uses the
// legacy validation scheme regardless of the value of NameValidationScheme.
// This function, however, does not use MetricNameRE for the check but a much
// faster hardcoded implementation.
//
// Deprecated: This function should not be used and might be removed in the future.
// Use [LegacyValidation.IsValidMetricName] instead.
func IsValidLegacyMetricName(n string) bool {
if len(n) == 0 {
return false
}
for i, b := range n {
if !isValidLegacyRune(b, i) {
return false
}
}
return true
return LegacyValidation.IsValidMetricName(n)
}

// EscapeMetricFamily escapes the given metric names and labels with the given
Expand Down
45 changes: 31 additions & 14 deletions model/metric_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ package model

import (
"errors"
"fmt"
"strings"
"testing"

Expand Down Expand Up @@ -202,9 +203,9 @@ func TestValidationScheme_UnmarshalYAML(t *testing.T) {
}
}

func TestMetricNameIsLegacyValid(t *testing.T) {
func TestValidationScheme_IsMetricNameValid(t *testing.T) {
scenarios := []struct {
mn LabelValue
mn string
legacyValid bool
utf8Valid bool
}{
Expand Down Expand Up @@ -259,19 +260,35 @@ func TestMetricNameIsLegacyValid(t *testing.T) {
utf8Valid: false,
},
}

for _, s := range scenarios {
NameValidationScheme = LegacyValidation
if IsValidMetricName(s.mn) != s.legacyValid {
t.Errorf("Expected %v for %q using legacy IsValidMetricName method", s.legacyValid, s.mn)
}
if MetricNameRE.MatchString(string(s.mn)) != s.legacyValid {
t.Errorf("Expected %v for %q using regexp matching", s.legacyValid, s.mn)
}
NameValidationScheme = UTF8Validation
if IsValidMetricName(s.mn) != s.utf8Valid {
t.Errorf("Expected %v for %q using utf-8 IsValidMetricName method", s.legacyValid, s.mn)
}
t.Run(fmt.Sprintf("%s,%t,%t", s.mn, s.legacyValid, s.utf8Valid), func(t *testing.T) {
if LegacyValidation.IsValidMetricName(s.mn) != s.legacyValid {
t.Errorf("Expected %v for %q using LegacyValidation.IsValidMetricName", s.legacyValid, s.mn)
}
if MetricNameRE.MatchString(string(s.mn)) != s.legacyValid {
t.Errorf("Expected %v for %q using regexp matching", s.legacyValid, s.mn)
}
if UTF8Validation.IsValidMetricName(s.mn) != s.utf8Valid {
t.Errorf("Expected %v for %q using UTF8Validation.IsValidMetricName", s.utf8Valid, s.mn)
}

// Test deprecated functions.
if IsValidLegacyMetricName(s.mn) != s.legacyValid {
t.Errorf("Expected %v for %q using IsValidLegacyMetricNames", s.legacyValid, s.mn)
}
origScheme := NameValidationScheme
t.Cleanup(func() {
NameValidationScheme = origScheme
})
NameValidationScheme = LegacyValidation
if IsValidMetricName(LabelValue(s.mn)) != s.legacyValid {
t.Errorf("Expected %v for %q using legacy IsValidMetricName", s.legacyValid, s.mn)
}
NameValidationScheme = UTF8Validation
if IsValidMetricName(LabelValue(s.mn)) != s.utf8Valid {
t.Errorf("Expected %v for %q using utf-8 IsValidMetricName method", s.utf8Valid, s.mn)
}
})
}
}

Expand Down
Loading