diff --git a/config/flipt.schema.cue b/config/flipt.schema.cue index 7e6df2c3ec..c5879bf128 100644 --- a/config/flipt.schema.cue +++ b/config/flipt.schema.cue @@ -287,9 +287,9 @@ import "strings" } #tracing: { - enabled?: bool | *false - exporter?: *"jaeger" | "zipkin" | "otlp" - samplingRatio?: float & >=0 & <=1 | *1 + enabled?: bool | *false + exporter?: *"jaeger" | "zipkin" | "otlp" + sampling_ratio?: float & >=0 & <=1 | *1 propagators?: [ ..."tracecontext" | "baggage" | "b3" | "b3multi" | "jaeger" | "xray" | "ottrace" | "none", ] | *["tracecontext", "baggage"] diff --git a/config/flipt.schema.json b/config/flipt.schema.json index daddf37bdd..02751b5170 100644 --- a/config/flipt.schema.json +++ b/config/flipt.schema.json @@ -995,7 +995,7 @@ "enum": ["jaeger", "zipkin", "otlp"], "default": "jaeger" }, - "samplingRatio": { + "sampling_ratio": { "type": "number", "default": 1, "minimum": 0, diff --git a/go.mod b/go.mod index 062ed45360..104cef3d11 100644 --- a/go.mod +++ b/go.mod @@ -44,6 +44,7 @@ require ( github.com/hashicorp/cap v0.6.0 github.com/hashicorp/go-multierror v1.1.1 github.com/hashicorp/golang-lru/v2 v2.0.7 + github.com/iancoleman/strcase v0.3.0 github.com/jackc/pgx/v5 v5.5.5 github.com/libsql/libsql-client-go v0.0.0-20230917132930-48c310b27e7b github.com/magefile/mage v1.15.0 diff --git a/go.sum b/go.sum index 67712b0506..1639a377af 100644 --- a/go.sum +++ b/go.sum @@ -384,6 +384,8 @@ github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog= github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI= +github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 343cb3f596..8f9bbeae41 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -15,6 +15,7 @@ import ( "testing" "time" + "github.com/iancoleman/strcase" "github.com/santhosh-tekuri/jsonschema/v5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -1502,3 +1503,95 @@ func TestGetConfigFile(t *testing.T) { }) } } + +var ( + // add any struct tags to match their camelCase equivalents here. + camelCaseMatchers = map[string]string{ + "requireTLS": "requireTLS", + "discoveryURL": "discoveryURL", + } +) + +func TestStructTags(t *testing.T) { + configType := reflect.TypeOf(Config{}) + configTags := getStructTags(configType) + + for k, v := range camelCaseMatchers { + strcase.ConfigureAcronym(k, v) + } + + // Validate the struct tags for the Config struct. + // recursively validate the struct tags for all sub-structs. + validateStructTags(t, configTags, configType) +} + +func validateStructTags(t *testing.T, tags map[string]map[string]string, tType reflect.Type) { + tName := tType.Name() + for fieldName, fieldTags := range tags { + fieldType, ok := tType.FieldByName(fieldName) + require.True(t, ok, "field %s not found in type %s", fieldName, tName) + + // Validate the `json` struct tag. + jsonTag, ok := fieldTags["json"] + if ok { + require.True(t, isCamelCase(jsonTag), "json tag for field '%s.%s' should be camelCase but is '%s'", tName, fieldName, jsonTag) + } + + // Validate the `mapstructure` struct tag. + mapstructureTag, ok := fieldTags["mapstructure"] + if ok { + require.True(t, isSnakeCase(mapstructureTag), "mapstructure tag for field '%s.%s' should be snake_case but is '%s'", tName, fieldName, mapstructureTag) + } + + // Validate the `yaml` struct tag. + yamlTag, ok := fieldTags["yaml"] + if ok { + require.True(t, isSnakeCase(yamlTag), "yaml tag for field '%s.%s' should be snake_case but is '%s'", tName, fieldName, yamlTag) + } + + // recursively validate the struct tags for all sub-structs. + if fieldType.Type.Kind() == reflect.Struct { + validateStructTags(t, getStructTags(fieldType.Type), fieldType.Type) + } + } +} + +func isCamelCase(s string) bool { + return s == strcase.ToLowerCamel(s) +} + +func isSnakeCase(s string) bool { + return s == strcase.ToSnake(s) +} + +func getStructTags(t reflect.Type) map[string]map[string]string { + tags := make(map[string]map[string]string) + + for i := 0; i < t.NumField(); i++ { + field := t.Field(i) + + // Get the field name. + fieldName := field.Name + + // Get the field tags. + fieldTags := make(map[string]string) + for _, tag := range []string{"json", "mapstructure", "yaml"} { + tagValue := field.Tag.Get(tag) + if tagValue == "-" { + fieldTags[tag] = "skip" + continue + } + values := strings.Split(tagValue, ",") + if len(values) > 1 { + tagValue = values[0] + } + if tagValue != "" { + fieldTags[tag] = tagValue + } + } + + tags[fieldName] = fieldTags + } + + return tags +} diff --git a/internal/config/testdata/tracing/otlp.yml b/internal/config/testdata/tracing/otlp.yml index 15a1125ee7..942098392b 100644 --- a/internal/config/testdata/tracing/otlp.yml +++ b/internal/config/testdata/tracing/otlp.yml @@ -1,7 +1,7 @@ tracing: enabled: true exporter: otlp - samplingRatio: 0.5 + sampling_ratio: 0.5 otlp: endpoint: http://localhost:9999 headers: diff --git a/internal/config/testdata/tracing/wrong_sampling_ratio.yml b/internal/config/testdata/tracing/wrong_sampling_ratio.yml index 12d40e4a00..f52714ac7c 100644 --- a/internal/config/testdata/tracing/wrong_sampling_ratio.yml +++ b/internal/config/testdata/tracing/wrong_sampling_ratio.yml @@ -1,3 +1,3 @@ tracing: enabled: true - samplingRatio: 1.1 + sampling_ratio: 1.1 diff --git a/internal/config/tracing.go b/internal/config/tracing.go index f771ddde81..e613037b99 100644 --- a/internal/config/tracing.go +++ b/internal/config/tracing.go @@ -17,7 +17,7 @@ type TracingConfig struct { Enabled bool `json:"enabled" mapstructure:"enabled" yaml:"enabled"` Exporter TracingExporter `json:"exporter,omitempty" mapstructure:"exporter" yaml:"exporter,omitempty"` Propagators []TracingPropagator `json:"propagators,omitempty" mapstructure:"propagators" yaml:"propagators,omitempty"` - SamplingRatio float64 `json:"samplingRatio,omitempty" mapstructure:"samplingRatio" yaml:"samplingRatio,omitempty"` + SamplingRatio float64 `json:"samplingRatio,omitempty" mapstructure:"sampling_ratio" yaml:"sampling_ratio,omitempty"` Jaeger JaegerTracingConfig `json:"jaeger,omitempty" mapstructure:"jaeger" yaml:"jaeger,omitempty"` Zipkin ZipkinTracingConfig `json:"zipkin,omitempty" mapstructure:"zipkin" yaml:"zipkin,omitempty"` OTLP OTLPTracingConfig `json:"otlp,omitempty" mapstructure:"otlp" yaml:"otlp,omitempty"` @@ -25,9 +25,9 @@ type TracingConfig struct { func (c *TracingConfig) setDefaults(v *viper.Viper) error { v.SetDefault("tracing", map[string]any{ - "enabled": false, - "exporter": TracingJaeger, - "samplingRatio": 1, + "enabled": false, + "exporter": TracingJaeger, + "sampling_ratio": 1, "propagators": []TracingPropagator{ TracingPropagatorTraceContext, TracingPropagatorBaggage,