Skip to content
Merged
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm

- `ParseYAML` in `go.opentelemetry.io/contrib/otelconf` now supports environment variables substitution in the format `${[env:]VAR_NAME[:-defaultvalue]}`. (#6215)
- Introduce v1.0.0-rc.2 model in `go.opentelemetry.io/contrib/otelconf`. (#8031)
- Add unmarshaling and validation for `CardinalityLimits` and `SpanLimits` to v1.0.0 model in `go.opentelemetry.io/contrib/otelconf`. (#8043)

### Removed

Expand Down
96 changes: 96 additions & 0 deletions otelconf/config_common.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package otelconf // import "go.opentelemetry.io/contrib/otelconf"

import (
"errors"
"fmt"
)

var (
errUnmarshalingCardinalityLimits = errors.New("unmarshaling cardinality_limit")
errUnmarshalingSpanLimits = errors.New("unmarshaling span_limit")
)

type errBound struct {
Field string
Bound int
Op string
}

func (e *errBound) Error() string {
return fmt.Sprintf("field %s: must be %s %d", e.Field, e.Op, e.Bound)
}

func (e *errBound) Is(target error) bool {
t, ok := target.(*errBound)
if !ok {
return false
}
return e.Field == t.Field && e.Bound == t.Bound && e.Op == t.Op
}

// newErrGreaterOrEqualZero creates a new error indicating that the field must be greater than
// or equal to zero.
func newErrGreaterOrEqualZero(field string) error {
return &errBound{Field: field, Bound: 0, Op: ">="}
}

// newErrGreaterThanZero creates a new error indicating that the field must be greater
// than zero.
func newErrGreaterThanZero(field string) error {
return &errBound{Field: field, Bound: 0, Op: ">"}
}

// validateCardinalityLimits handles validation for CardinalityLimits.
func validateCardinalityLimits(plain *CardinalityLimits) error {
if plain.Counter != nil && 0 >= *plain.Counter {
return newErrGreaterThanZero("counter")
}
if plain.Default != nil && 0 >= *plain.Default {
return newErrGreaterThanZero("default")
}
if plain.Gauge != nil && 0 >= *plain.Gauge {
return newErrGreaterThanZero("gauge")
}
if plain.Histogram != nil && 0 >= *plain.Histogram {
return newErrGreaterThanZero("histogram")
}
if plain.ObservableCounter != nil && 0 >= *plain.ObservableCounter {
return newErrGreaterThanZero("observable_counter")
}
if plain.ObservableGauge != nil && 0 >= *plain.ObservableGauge {
return newErrGreaterThanZero("observable_gauge")
}
if plain.ObservableUpDownCounter != nil && 0 >= *plain.ObservableUpDownCounter {
return newErrGreaterThanZero("observable_up_down_counter")
}
if plain.UpDownCounter != nil && 0 >= *plain.UpDownCounter {
return newErrGreaterThanZero("up_down_counter")
}
Comment thread
dmathieu marked this conversation as resolved.
return nil
}

// validateSpanLimits handles validation for SpanLimits.
func validateSpanLimits(plain *SpanLimits) error {
if plain.AttributeCountLimit != nil && 0 > *plain.AttributeCountLimit {
return newErrGreaterOrEqualZero("attribute_count_limit")
}
if plain.AttributeValueLengthLimit != nil && 0 > *plain.AttributeValueLengthLimit {
return newErrGreaterOrEqualZero("attribute_value_length_limit")
}
if plain.EventAttributeCountLimit != nil && 0 > *plain.EventAttributeCountLimit {
return newErrGreaterOrEqualZero("event_attribute_count_limit")
}
if plain.EventCountLimit != nil && 0 > *plain.EventCountLimit {
return newErrGreaterOrEqualZero("event_count_limit")
}
if plain.LinkAttributeCountLimit != nil && 0 > *plain.LinkAttributeCountLimit {
return newErrGreaterOrEqualZero("link_attribute_count_limit")
}
if plain.LinkCountLimit != nil && 0 > *plain.LinkCountLimit {
return newErrGreaterOrEqualZero("link_count_limit")
}
return nil
}
37 changes: 37 additions & 0 deletions otelconf/config_json.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package otelconf // import "go.opentelemetry.io/contrib/otelconf"

import (
"encoding/json"
"errors"
)

// UnmarshalJSON implements json.Unmarshaler.
func (j *CardinalityLimits) UnmarshalJSON(value []byte) error {
type Plain CardinalityLimits
var plain Plain
if err := json.Unmarshal(value, &plain); err != nil {
return errors.Join(errUnmarshalingCardinalityLimits, err)
}
if err := validateCardinalityLimits((*CardinalityLimits)(&plain)); err != nil {
return err
}
*j = CardinalityLimits(plain)
return nil
}

// UnmarshalJSON implements json.Unmarshaler.
func (j *SpanLimits) UnmarshalJSON(value []byte) error {
type Plain SpanLimits
var plain Plain
if err := json.Unmarshal(value, &plain); err != nil {
return errors.Join(errUnmarshalingSpanLimits, err)
}
if err := validateSpanLimits((*SpanLimits)(&plain)); err != nil {
return err
}
*j = SpanLimits(plain)
return nil
}
225 changes: 225 additions & 0 deletions otelconf/config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package otelconf

import (
"testing"

"github.com/stretchr/testify/assert"
"go.yaml.in/yaml/v3"
)

func TestUnmarshalCardinalityLimits(t *testing.T) {
for _, tt := range []struct {
name string
yamlConfig []byte
jsonConfig []byte
wantErrT error
}{
{
name: "valid with all fields positive",
jsonConfig: []byte(`{"counter":100,"default":200,"gauge":300,"histogram":400,"observable_counter":500,"observable_gauge":600,"observable_up_down_counter":700,"up_down_counter":800}`),
yamlConfig: []byte("counter: 100\ndefault: 200\ngauge: 300\nhistogram: 400\nobservable_counter: 500\nobservable_gauge: 600\nobservable_up_down_counter: 700\nup_down_counter: 800"),
},
{
name: "valid with single field",
jsonConfig: []byte(`{"default":2000}`),
yamlConfig: []byte("default: 2000"),
},
{
name: "valid empty",
jsonConfig: []byte(`{}`),
yamlConfig: []byte("{}"),
},
{
name: "invalid data",
jsonConfig: []byte(`{:2000}`),
yamlConfig: []byte("counter: !!str 2000"),
wantErrT: errUnmarshalingCardinalityLimits,
},
{
name: "invalid counter zero",
jsonConfig: []byte(`{"counter":0}`),
yamlConfig: []byte("counter: 0"),
wantErrT: newErrGreaterThanZero("counter"),
},
{
name: "invalid counter negative",
jsonConfig: []byte(`{"counter":-1}`),
yamlConfig: []byte("counter: -1"),
wantErrT: newErrGreaterThanZero("counter"),
},
{
name: "invalid default zero",
jsonConfig: []byte(`{"default":0}`),
yamlConfig: []byte("default: 0"),
wantErrT: newErrGreaterThanZero("default"),
},
{
name: "invalid default negative",
jsonConfig: []byte(`{"default":-1}`),
yamlConfig: []byte("default: -1"),
wantErrT: newErrGreaterThanZero("default"),
},
{
name: "invalid gauge zero",
jsonConfig: []byte(`{"gauge":0}`),
yamlConfig: []byte("gauge: 0"),
wantErrT: newErrGreaterThanZero("gauge"),
},
{
name: "invalid gauge negative",
jsonConfig: []byte(`{"gauge":-1}`),
yamlConfig: []byte("gauge: -1"),
wantErrT: newErrGreaterThanZero("gauge"),
},
{
name: "invalid histogram zero",
jsonConfig: []byte(`{"histogram":0}`),
yamlConfig: []byte("histogram: 0"),
wantErrT: newErrGreaterThanZero("histogram"),
},
{
name: "invalid histogram negative",
jsonConfig: []byte(`{"histogram":-1}`),
yamlConfig: []byte("histogram: -1"),
wantErrT: newErrGreaterThanZero("histogram"),
},
{
name: "invalid observable_counter zero",
jsonConfig: []byte(`{"observable_counter":0}`),
yamlConfig: []byte("observable_counter: 0"),
wantErrT: newErrGreaterThanZero("observable_counter"),
},
{
name: "invalid observable_counter negative",
jsonConfig: []byte(`{"observable_counter":-1}`),
yamlConfig: []byte("observable_counter: -1"),
wantErrT: newErrGreaterThanZero("observable_counter"),
},
{
name: "invalid observable_gauge zero",
jsonConfig: []byte(`{"observable_gauge":0}`),
yamlConfig: []byte("observable_gauge: 0"),
wantErrT: newErrGreaterThanZero("observable_gauge"),
},
{
name: "invalid observable_gauge negative",
jsonConfig: []byte(`{"observable_gauge":-1}`),
yamlConfig: []byte("observable_gauge: -1"),
wantErrT: newErrGreaterThanZero("observable_gauge"),
},
{
name: "invalid observable_up_down_counter zero",
jsonConfig: []byte(`{"observable_up_down_counter":0}`),
yamlConfig: []byte("observable_up_down_counter: 0"),
wantErrT: newErrGreaterThanZero("observable_up_down_counter"),
},
{
name: "invalid observable_up_down_counter negative",
jsonConfig: []byte(`{"observable_up_down_counter":-1}`),
yamlConfig: []byte("observable_up_down_counter: -1"),
wantErrT: newErrGreaterThanZero("observable_up_down_counter"),
},
{
name: "invalid up_down_counter zero",
jsonConfig: []byte(`{"up_down_counter":0}`),
yamlConfig: []byte("up_down_counter: 0"),
wantErrT: newErrGreaterThanZero("up_down_counter"),
},
{
name: "invalid up_down_counter negative",
jsonConfig: []byte(`{"up_down_counter":-1}`),
yamlConfig: []byte("up_down_counter: -1"),
wantErrT: newErrGreaterThanZero("up_down_counter"),
},
} {
t.Run(tt.name, func(t *testing.T) {
cl := CardinalityLimits{}
err := cl.UnmarshalJSON(tt.jsonConfig)
assert.ErrorIs(t, err, tt.wantErrT)

cl = CardinalityLimits{}
err = yaml.Unmarshal(tt.yamlConfig, &cl)
assert.ErrorIs(t, err, tt.wantErrT)
})
}
}

func TestUnmarshalSpanLimits(t *testing.T) {
for _, tt := range []struct {
name string
yamlConfig []byte
jsonConfig []byte
wantErrT error
}{
{
name: "valid with all fields positive",
jsonConfig: []byte(`{"attribute_count_limit":100,"attribute_value_length_limit":200,"event_attribute_count_limit":300,"event_count_limit":400,"link_attribute_count_limit":500,"link_count_limit":600}`),
yamlConfig: []byte("attribute_count_limit: 100\nattribute_value_length_limit: 200\nevent_attribute_count_limit: 300\nevent_count_limit: 400\nlink_attribute_count_limit: 500\nlink_count_limit: 600"),
},
{
name: "valid with single field",
jsonConfig: []byte(`{"attribute_value_length_limit":2000}`),
yamlConfig: []byte("attribute_value_length_limit: 2000"),
},
{
name: "valid empty",
jsonConfig: []byte(`{}`),
yamlConfig: []byte("{}"),
},
{
name: "invalid data",
jsonConfig: []byte(`{:2000}`),
yamlConfig: []byte("attribute_count_limit: !!str 2000"),
wantErrT: errUnmarshalingSpanLimits,
},
{
name: "invalid attribute_count_limit negative",
jsonConfig: []byte(`{"attribute_count_limit":-1}`),
yamlConfig: []byte("attribute_count_limit: -1"),
wantErrT: newErrGreaterOrEqualZero("attribute_count_limit"),
},
{
name: "invalid attribute_value_length_limit negative",
jsonConfig: []byte(`{"attribute_value_length_limit":-1}`),
yamlConfig: []byte("attribute_value_length_limit: -1"),
wantErrT: newErrGreaterOrEqualZero("attribute_value_length_limit"),
},
{
name: "invalid event_attribute_count_limit negative",
jsonConfig: []byte(`{"event_attribute_count_limit":-1}`),
yamlConfig: []byte("event_attribute_count_limit: -1"),
wantErrT: newErrGreaterOrEqualZero("event_attribute_count_limit"),
},
{
name: "invalid event_count_limit negative",
jsonConfig: []byte(`{"event_count_limit":-1}`),
yamlConfig: []byte("event_count_limit: -1"),
wantErrT: newErrGreaterOrEqualZero("event_count_limit"),
},
{
name: "invalid link_attribute_count_limit negative",
jsonConfig: []byte(`{"link_attribute_count_limit":-1}`),
yamlConfig: []byte("link_attribute_count_limit: -1"),
wantErrT: newErrGreaterOrEqualZero("link_attribute_count_limit"),
},
{
name: "invalid link_count_limit negative",
jsonConfig: []byte(`{"link_count_limit":-1}`),
yamlConfig: []byte("link_count_limit: -1"),
wantErrT: newErrGreaterOrEqualZero("link_count_limit"),
},
} {
t.Run(tt.name, func(t *testing.T) {
cl := SpanLimits{}
err := cl.UnmarshalJSON(tt.jsonConfig)
assert.ErrorIs(t, err, tt.wantErrT)

cl = SpanLimits{}
err = yaml.Unmarshal(tt.yamlConfig, &cl)
assert.ErrorIs(t, err, tt.wantErrT)
})
}
}
Loading