Skip to content

Commit 1f0bb77

Browse files
codebotenMrAlias
andauthored
otelconf: add unmarshaling and validation for span & cardinality limits (open-telemetry#8043)
Signed-off-by: alex boten <[email protected]> Co-authored-by: Tyler Yahn <[email protected]>
1 parent bbc51b2 commit 1f0bb77

File tree

5 files changed

+397
-0
lines changed

5 files changed

+397
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
1212

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

1617
### Removed
1718

otelconf/config_common.go

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package otelconf // import "go.opentelemetry.io/contrib/otelconf"
5+
6+
import (
7+
"errors"
8+
"fmt"
9+
)
10+
11+
var (
12+
errUnmarshalingCardinalityLimits = errors.New("unmarshaling cardinality_limit")
13+
errUnmarshalingSpanLimits = errors.New("unmarshaling span_limit")
14+
)
15+
16+
type errBound struct {
17+
Field string
18+
Bound int
19+
Op string
20+
}
21+
22+
func (e *errBound) Error() string {
23+
return fmt.Sprintf("field %s: must be %s %d", e.Field, e.Op, e.Bound)
24+
}
25+
26+
func (e *errBound) Is(target error) bool {
27+
t, ok := target.(*errBound)
28+
if !ok {
29+
return false
30+
}
31+
return e.Field == t.Field && e.Bound == t.Bound && e.Op == t.Op
32+
}
33+
34+
// newErrGreaterOrEqualZero creates a new error indicating that the field must be greater than
35+
// or equal to zero.
36+
func newErrGreaterOrEqualZero(field string) error {
37+
return &errBound{Field: field, Bound: 0, Op: ">="}
38+
}
39+
40+
// newErrGreaterThanZero creates a new error indicating that the field must be greater
41+
// than zero.
42+
func newErrGreaterThanZero(field string) error {
43+
return &errBound{Field: field, Bound: 0, Op: ">"}
44+
}
45+
46+
// validateCardinalityLimits handles validation for CardinalityLimits.
47+
func validateCardinalityLimits(plain *CardinalityLimits) error {
48+
if plain.Counter != nil && 0 >= *plain.Counter {
49+
return newErrGreaterThanZero("counter")
50+
}
51+
if plain.Default != nil && 0 >= *plain.Default {
52+
return newErrGreaterThanZero("default")
53+
}
54+
if plain.Gauge != nil && 0 >= *plain.Gauge {
55+
return newErrGreaterThanZero("gauge")
56+
}
57+
if plain.Histogram != nil && 0 >= *plain.Histogram {
58+
return newErrGreaterThanZero("histogram")
59+
}
60+
if plain.ObservableCounter != nil && 0 >= *plain.ObservableCounter {
61+
return newErrGreaterThanZero("observable_counter")
62+
}
63+
if plain.ObservableGauge != nil && 0 >= *plain.ObservableGauge {
64+
return newErrGreaterThanZero("observable_gauge")
65+
}
66+
if plain.ObservableUpDownCounter != nil && 0 >= *plain.ObservableUpDownCounter {
67+
return newErrGreaterThanZero("observable_up_down_counter")
68+
}
69+
if plain.UpDownCounter != nil && 0 >= *plain.UpDownCounter {
70+
return newErrGreaterThanZero("up_down_counter")
71+
}
72+
return nil
73+
}
74+
75+
// validateSpanLimits handles validation for SpanLimits.
76+
func validateSpanLimits(plain *SpanLimits) error {
77+
if plain.AttributeCountLimit != nil && 0 > *plain.AttributeCountLimit {
78+
return newErrGreaterOrEqualZero("attribute_count_limit")
79+
}
80+
if plain.AttributeValueLengthLimit != nil && 0 > *plain.AttributeValueLengthLimit {
81+
return newErrGreaterOrEqualZero("attribute_value_length_limit")
82+
}
83+
if plain.EventAttributeCountLimit != nil && 0 > *plain.EventAttributeCountLimit {
84+
return newErrGreaterOrEqualZero("event_attribute_count_limit")
85+
}
86+
if plain.EventCountLimit != nil && 0 > *plain.EventCountLimit {
87+
return newErrGreaterOrEqualZero("event_count_limit")
88+
}
89+
if plain.LinkAttributeCountLimit != nil && 0 > *plain.LinkAttributeCountLimit {
90+
return newErrGreaterOrEqualZero("link_attribute_count_limit")
91+
}
92+
if plain.LinkCountLimit != nil && 0 > *plain.LinkCountLimit {
93+
return newErrGreaterOrEqualZero("link_count_limit")
94+
}
95+
return nil
96+
}

otelconf/config_json.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package otelconf // import "go.opentelemetry.io/contrib/otelconf"
5+
6+
import (
7+
"encoding/json"
8+
"errors"
9+
)
10+
11+
// UnmarshalJSON implements json.Unmarshaler.
12+
func (j *CardinalityLimits) UnmarshalJSON(value []byte) error {
13+
type Plain CardinalityLimits
14+
var plain Plain
15+
if err := json.Unmarshal(value, &plain); err != nil {
16+
return errors.Join(errUnmarshalingCardinalityLimits, err)
17+
}
18+
if err := validateCardinalityLimits((*CardinalityLimits)(&plain)); err != nil {
19+
return err
20+
}
21+
*j = CardinalityLimits(plain)
22+
return nil
23+
}
24+
25+
// UnmarshalJSON implements json.Unmarshaler.
26+
func (j *SpanLimits) UnmarshalJSON(value []byte) error {
27+
type Plain SpanLimits
28+
var plain Plain
29+
if err := json.Unmarshal(value, &plain); err != nil {
30+
return errors.Join(errUnmarshalingSpanLimits, err)
31+
}
32+
if err := validateSpanLimits((*SpanLimits)(&plain)); err != nil {
33+
return err
34+
}
35+
*j = SpanLimits(plain)
36+
return nil
37+
}

otelconf/config_test.go

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package otelconf
5+
6+
import (
7+
"testing"
8+
9+
"github.com/stretchr/testify/assert"
10+
"go.yaml.in/yaml/v3"
11+
)
12+
13+
func TestUnmarshalCardinalityLimits(t *testing.T) {
14+
for _, tt := range []struct {
15+
name string
16+
yamlConfig []byte
17+
jsonConfig []byte
18+
wantErrT error
19+
}{
20+
{
21+
name: "valid with all fields positive",
22+
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}`),
23+
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"),
24+
},
25+
{
26+
name: "valid with single field",
27+
jsonConfig: []byte(`{"default":2000}`),
28+
yamlConfig: []byte("default: 2000"),
29+
},
30+
{
31+
name: "valid empty",
32+
jsonConfig: []byte(`{}`),
33+
yamlConfig: []byte("{}"),
34+
},
35+
{
36+
name: "invalid data",
37+
jsonConfig: []byte(`{:2000}`),
38+
yamlConfig: []byte("counter: !!str 2000"),
39+
wantErrT: errUnmarshalingCardinalityLimits,
40+
},
41+
{
42+
name: "invalid counter zero",
43+
jsonConfig: []byte(`{"counter":0}`),
44+
yamlConfig: []byte("counter: 0"),
45+
wantErrT: newErrGreaterThanZero("counter"),
46+
},
47+
{
48+
name: "invalid counter negative",
49+
jsonConfig: []byte(`{"counter":-1}`),
50+
yamlConfig: []byte("counter: -1"),
51+
wantErrT: newErrGreaterThanZero("counter"),
52+
},
53+
{
54+
name: "invalid default zero",
55+
jsonConfig: []byte(`{"default":0}`),
56+
yamlConfig: []byte("default: 0"),
57+
wantErrT: newErrGreaterThanZero("default"),
58+
},
59+
{
60+
name: "invalid default negative",
61+
jsonConfig: []byte(`{"default":-1}`),
62+
yamlConfig: []byte("default: -1"),
63+
wantErrT: newErrGreaterThanZero("default"),
64+
},
65+
{
66+
name: "invalid gauge zero",
67+
jsonConfig: []byte(`{"gauge":0}`),
68+
yamlConfig: []byte("gauge: 0"),
69+
wantErrT: newErrGreaterThanZero("gauge"),
70+
},
71+
{
72+
name: "invalid gauge negative",
73+
jsonConfig: []byte(`{"gauge":-1}`),
74+
yamlConfig: []byte("gauge: -1"),
75+
wantErrT: newErrGreaterThanZero("gauge"),
76+
},
77+
{
78+
name: "invalid histogram zero",
79+
jsonConfig: []byte(`{"histogram":0}`),
80+
yamlConfig: []byte("histogram: 0"),
81+
wantErrT: newErrGreaterThanZero("histogram"),
82+
},
83+
{
84+
name: "invalid histogram negative",
85+
jsonConfig: []byte(`{"histogram":-1}`),
86+
yamlConfig: []byte("histogram: -1"),
87+
wantErrT: newErrGreaterThanZero("histogram"),
88+
},
89+
{
90+
name: "invalid observable_counter zero",
91+
jsonConfig: []byte(`{"observable_counter":0}`),
92+
yamlConfig: []byte("observable_counter: 0"),
93+
wantErrT: newErrGreaterThanZero("observable_counter"),
94+
},
95+
{
96+
name: "invalid observable_counter negative",
97+
jsonConfig: []byte(`{"observable_counter":-1}`),
98+
yamlConfig: []byte("observable_counter: -1"),
99+
wantErrT: newErrGreaterThanZero("observable_counter"),
100+
},
101+
{
102+
name: "invalid observable_gauge zero",
103+
jsonConfig: []byte(`{"observable_gauge":0}`),
104+
yamlConfig: []byte("observable_gauge: 0"),
105+
wantErrT: newErrGreaterThanZero("observable_gauge"),
106+
},
107+
{
108+
name: "invalid observable_gauge negative",
109+
jsonConfig: []byte(`{"observable_gauge":-1}`),
110+
yamlConfig: []byte("observable_gauge: -1"),
111+
wantErrT: newErrGreaterThanZero("observable_gauge"),
112+
},
113+
{
114+
name: "invalid observable_up_down_counter zero",
115+
jsonConfig: []byte(`{"observable_up_down_counter":0}`),
116+
yamlConfig: []byte("observable_up_down_counter: 0"),
117+
wantErrT: newErrGreaterThanZero("observable_up_down_counter"),
118+
},
119+
{
120+
name: "invalid observable_up_down_counter negative",
121+
jsonConfig: []byte(`{"observable_up_down_counter":-1}`),
122+
yamlConfig: []byte("observable_up_down_counter: -1"),
123+
wantErrT: newErrGreaterThanZero("observable_up_down_counter"),
124+
},
125+
{
126+
name: "invalid up_down_counter zero",
127+
jsonConfig: []byte(`{"up_down_counter":0}`),
128+
yamlConfig: []byte("up_down_counter: 0"),
129+
wantErrT: newErrGreaterThanZero("up_down_counter"),
130+
},
131+
{
132+
name: "invalid up_down_counter negative",
133+
jsonConfig: []byte(`{"up_down_counter":-1}`),
134+
yamlConfig: []byte("up_down_counter: -1"),
135+
wantErrT: newErrGreaterThanZero("up_down_counter"),
136+
},
137+
} {
138+
t.Run(tt.name, func(t *testing.T) {
139+
cl := CardinalityLimits{}
140+
err := cl.UnmarshalJSON(tt.jsonConfig)
141+
assert.ErrorIs(t, err, tt.wantErrT)
142+
143+
cl = CardinalityLimits{}
144+
err = yaml.Unmarshal(tt.yamlConfig, &cl)
145+
assert.ErrorIs(t, err, tt.wantErrT)
146+
})
147+
}
148+
}
149+
150+
func TestUnmarshalSpanLimits(t *testing.T) {
151+
for _, tt := range []struct {
152+
name string
153+
yamlConfig []byte
154+
jsonConfig []byte
155+
wantErrT error
156+
}{
157+
{
158+
name: "valid with all fields positive",
159+
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}`),
160+
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"),
161+
},
162+
{
163+
name: "valid with single field",
164+
jsonConfig: []byte(`{"attribute_value_length_limit":2000}`),
165+
yamlConfig: []byte("attribute_value_length_limit: 2000"),
166+
},
167+
{
168+
name: "valid empty",
169+
jsonConfig: []byte(`{}`),
170+
yamlConfig: []byte("{}"),
171+
},
172+
{
173+
name: "invalid data",
174+
jsonConfig: []byte(`{:2000}`),
175+
yamlConfig: []byte("attribute_count_limit: !!str 2000"),
176+
wantErrT: errUnmarshalingSpanLimits,
177+
},
178+
{
179+
name: "invalid attribute_count_limit negative",
180+
jsonConfig: []byte(`{"attribute_count_limit":-1}`),
181+
yamlConfig: []byte("attribute_count_limit: -1"),
182+
wantErrT: newErrGreaterOrEqualZero("attribute_count_limit"),
183+
},
184+
{
185+
name: "invalid attribute_value_length_limit negative",
186+
jsonConfig: []byte(`{"attribute_value_length_limit":-1}`),
187+
yamlConfig: []byte("attribute_value_length_limit: -1"),
188+
wantErrT: newErrGreaterOrEqualZero("attribute_value_length_limit"),
189+
},
190+
{
191+
name: "invalid event_attribute_count_limit negative",
192+
jsonConfig: []byte(`{"event_attribute_count_limit":-1}`),
193+
yamlConfig: []byte("event_attribute_count_limit: -1"),
194+
wantErrT: newErrGreaterOrEqualZero("event_attribute_count_limit"),
195+
},
196+
{
197+
name: "invalid event_count_limit negative",
198+
jsonConfig: []byte(`{"event_count_limit":-1}`),
199+
yamlConfig: []byte("event_count_limit: -1"),
200+
wantErrT: newErrGreaterOrEqualZero("event_count_limit"),
201+
},
202+
{
203+
name: "invalid link_attribute_count_limit negative",
204+
jsonConfig: []byte(`{"link_attribute_count_limit":-1}`),
205+
yamlConfig: []byte("link_attribute_count_limit: -1"),
206+
wantErrT: newErrGreaterOrEqualZero("link_attribute_count_limit"),
207+
},
208+
{
209+
name: "invalid link_count_limit negative",
210+
jsonConfig: []byte(`{"link_count_limit":-1}`),
211+
yamlConfig: []byte("link_count_limit: -1"),
212+
wantErrT: newErrGreaterOrEqualZero("link_count_limit"),
213+
},
214+
} {
215+
t.Run(tt.name, func(t *testing.T) {
216+
cl := SpanLimits{}
217+
err := cl.UnmarshalJSON(tt.jsonConfig)
218+
assert.ErrorIs(t, err, tt.wantErrT)
219+
220+
cl = SpanLimits{}
221+
err = yaml.Unmarshal(tt.yamlConfig, &cl)
222+
assert.ErrorIs(t, err, tt.wantErrT)
223+
})
224+
}
225+
}

0 commit comments

Comments
 (0)