diff --git a/model/metric.go b/model/metric.go index 31981bb4..2aa78900 100644 --- a/model/metric.go +++ b/model/metric.go @@ -14,6 +14,7 @@ package model import ( + "encoding/json" "errors" "fmt" "regexp" @@ -77,10 +78,13 @@ const ( UTF8Validation ) -var ( - _ yaml.Marshaler = UnsetValidation - _ fmt.Stringer = UnsetValidation -) +var _ interface { + yaml.Marshaler + yaml.Unmarshaler + json.Marshaler + json.Unmarshaler + fmt.Stringer +} = new(ValidationScheme) // String returns the string representation of s. func (s ValidationScheme) String() string { @@ -114,15 +118,41 @@ func (s *ValidationScheme) UnmarshalYAML(unmarshal func(any) error) error { if err := unmarshal(&scheme); err != nil { return err } - switch scheme { + return s.Set(scheme) +} + +// MarshalJSON implements the json.Marshaler interface. +func (s ValidationScheme) MarshalJSON() ([]byte, error) { + switch s { + case UnsetValidation: + return json.Marshal("") + case UTF8Validation, LegacyValidation: + return json.Marshal(s.String()) + default: + return nil, fmt.Errorf("unhandled ValidationScheme: %d", s) + } +} + +// UnmarshalJSON implements the json.Unmarshaler interface. +func (s *ValidationScheme) UnmarshalJSON(bytes []byte) error { + var repr string + if err := json.Unmarshal(bytes, &repr); err != nil { + return err + } + return s.Set(repr) +} + +// Set implements the pflag.Value interface. +func (s *ValidationScheme) Set(text string) error { + switch text { case "": // Don't change the value. - case "legacy": + case LegacyValidation.String(): *s = LegacyValidation - case "utf8": + case UTF8Validation.String(): *s = UTF8Validation default: - return fmt.Errorf("unrecognized ValidationScheme: %q", scheme) + return fmt.Errorf("unrecognized ValidationScheme: %q", text) } return nil } @@ -174,6 +204,11 @@ func (s ValidationScheme) IsValidLabelName(labelName string) bool { } } +// Type implements the pflag.Value interface. +func (s ValidationScheme) Type() string { + return "validationScheme" +} + type EscapingScheme int const ( diff --git a/model/metric_test.go b/model/metric_test.go index b64c8a7e..4910cc25 100644 --- a/model/metric_test.go +++ b/model/metric_test.go @@ -14,6 +14,7 @@ package model import ( + "encoding/json" "errors" "fmt" "strings" @@ -203,6 +204,94 @@ func TestValidationScheme_UnmarshalYAML(t *testing.T) { } } +func TestValidationScheme_UnmarshalJSON(t *testing.T) { + testCases := []struct { + name string + input string + want ValidationScheme + wantErr bool + }{ + { + name: "invalid", + input: `invalid`, + wantErr: true, + }, + { + name: "empty", + input: `""`, + want: UnsetValidation, + }, + { + name: "legacy validation", + input: `"legacy"`, + want: LegacyValidation, + }, + { + name: "utf8 validation", + input: `"utf8"`, + want: UTF8Validation, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + var got ValidationScheme + err := json.Unmarshal([]byte(tc.input), &got) + if tc.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + require.Equal(t, tc.want, ValidationScheme(got)) + + output, err := json.Marshal(got) + require.NoError(t, err) + require.Equal(t, tc.input, string(output)) + }) + } +} + +func TestValidationScheme_Set(t *testing.T) { + testCases := []struct { + name string + input string + want ValidationScheme + wantErr bool + }{ + { + name: "invalid", + input: `invalid`, + wantErr: true, + }, + { + name: "empty", + input: ``, + want: UnsetValidation, + }, + { + name: "legacy validation", + input: `legacy`, + want: LegacyValidation, + }, + { + name: "utf8 validation", + input: `utf8`, + want: UTF8Validation, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + var got ValidationScheme + err := got.Set(tc.input) + if tc.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + require.Equal(t, tc.want, ValidationScheme(got)) + }) + } +} + func TestValidationScheme_IsMetricNameValid(t *testing.T) { scenarios := []struct { mn string