Skip to content

Commit 0336881

Browse files
markphelpsClaude
andauthored
fix: allow rollout and percentage zero values in YAML serialization (#4678)
Remove omitempty tag from Distribution.Rollout and ThresholdRule.Percentage fields to fix CUE validation errors when these fields have zero values. - Fixed Distribution.Rollout field to always serialize, even when 0 - Fixed ThresholdRule.Percentage field to always serialize, even when 0 - Added comprehensive tests for YAML and JSON serialization with zero values - Added CUE validation test case with zero values Fixes #4677 Signed-off-by: Claude <[email protected]> Co-authored-by: Claude <[email protected]>
1 parent 1841e10 commit 0336881

File tree

4 files changed

+261
-2
lines changed

4 files changed

+261
-2
lines changed
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
version: "1.5"
2+
flags:
3+
- key: test_variant_flag
4+
name: Test Variant Flag
5+
type: VARIANT_FLAG_TYPE
6+
enabled: true
7+
variants:
8+
- key: variant1
9+
name: Variant 1
10+
description: First variant
11+
- key: variant2
12+
name: Variant 2
13+
description: Second variant
14+
rules:
15+
- segment: test_segment
16+
distributions:
17+
- variant: variant1
18+
rollout: 0
19+
- variant: variant2
20+
rollout: 100
21+
- key: test_boolean_flag
22+
name: Test Boolean Flag
23+
type: BOOLEAN_FLAG_TYPE
24+
enabled: true
25+
rollouts:
26+
- description: "enabled for 0% threshold"
27+
threshold:
28+
percentage: 0
29+
value: true
30+
- description: "enabled for 0.0% threshold"
31+
threshold:
32+
percentage: 0.0
33+
value: true
34+
- description: "enabled for 50% threshold"
35+
threshold:
36+
percentage: 50
37+
value: true
38+
- description: "enabled for test segment with false value"
39+
segment:
40+
key: test_segment
41+
value: false
42+
43+
segments:
44+
- key: test_segment
45+
name: Test Segment
46+
match_type: ANY_MATCH_TYPE
47+
constraints:
48+
- type: STRING_COMPARISON_TYPE
49+
property: user_id
50+
operator: eq
51+
value: test_user

core/validation/validate_test.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,20 @@ func TestValidate_NamespaceDetails_v4(t *testing.T) {
9393
assert.NoError(t, err)
9494
}
9595

96+
func TestValidate_ZeroValues_Success(t *testing.T) {
97+
const file = "testdata/valid_zero_values.yaml"
98+
f, err := os.Open(file)
99+
require.NoError(t, err)
100+
101+
defer f.Close()
102+
103+
v, err := NewFeaturesValidator()
104+
require.NoError(t, err)
105+
106+
err = v.Validate(file, f)
107+
assert.NoError(t, err)
108+
}
109+
96110
func TestValidate_YAML_Stream(t *testing.T) {
97111
const file = "testdata/valid_yaml_stream.yaml"
98112
f, err := os.Open(file)

internal/ext/common.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ type Rule struct {
5050

5151
type Distribution struct {
5252
VariantKey string `yaml:"variant,omitempty" json:"variant,omitempty"`
53-
Rollout float32 `yaml:"rollout,omitempty" json:"rollout,omitempty"`
53+
Rollout float32 `yaml:"rollout" json:"rollout"`
5454
}
5555

5656
type Rollout struct {
@@ -137,7 +137,7 @@ func (s *SegmentRule) UnmarshalJSON(data []byte) error {
137137
}
138138

139139
type ThresholdRule struct {
140-
Percentage float32 `yaml:"percentage,omitempty" json:"percentage,omitempty"`
140+
Percentage float32 `yaml:"percentage" json:"percentage"`
141141
Value bool `yaml:"value,omitempty" json:"value,omitempty"`
142142
}
143143

internal/ext/common_test.go

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
package ext
2+
3+
import (
4+
"encoding/json"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
"github.com/stretchr/testify/require"
9+
"gopkg.in/yaml.v3"
10+
)
11+
12+
func TestDistribution_ZeroRollout_YAML(t *testing.T) {
13+
tests := []struct {
14+
name string
15+
rollout float32
16+
expected string
17+
}{
18+
{
19+
name: "zero rollout",
20+
rollout: 0,
21+
expected: "variant: test\nrollout: 0\n",
22+
},
23+
{
24+
name: "zero float rollout",
25+
rollout: 0.0,
26+
expected: "variant: test\nrollout: 0\n",
27+
},
28+
{
29+
name: "non-zero rollout",
30+
rollout: 50.0,
31+
expected: "variant: test\nrollout: 50\n",
32+
},
33+
}
34+
35+
for _, tt := range tests {
36+
t.Run(tt.name, func(t *testing.T) {
37+
dist := &Distribution{
38+
VariantKey: "test",
39+
Rollout: tt.rollout,
40+
}
41+
42+
data, err := yaml.Marshal(dist)
43+
require.NoError(t, err)
44+
assert.Equal(t, tt.expected, string(data))
45+
46+
// Test unmarshaling back
47+
var unmarshaled Distribution
48+
err = yaml.Unmarshal(data, &unmarshaled)
49+
require.NoError(t, err)
50+
assert.Equal(t, dist.VariantKey, unmarshaled.VariantKey)
51+
assert.InDelta(t, dist.Rollout, unmarshaled.Rollout, 0.001)
52+
})
53+
}
54+
}
55+
56+
func TestDistribution_ZeroRollout_JSON(t *testing.T) {
57+
tests := []struct {
58+
name string
59+
rollout float32
60+
expected string
61+
}{
62+
{
63+
name: "zero rollout",
64+
rollout: 0,
65+
expected: `{"variant":"test","rollout":0}`,
66+
},
67+
{
68+
name: "zero float rollout",
69+
rollout: 0.0,
70+
expected: `{"variant":"test","rollout":0}`,
71+
},
72+
{
73+
name: "non-zero rollout",
74+
rollout: 50.0,
75+
expected: `{"variant":"test","rollout":50}`,
76+
},
77+
}
78+
79+
for _, tt := range tests {
80+
t.Run(tt.name, func(t *testing.T) {
81+
dist := &Distribution{
82+
VariantKey: "test",
83+
Rollout: tt.rollout,
84+
}
85+
86+
data, err := json.Marshal(dist)
87+
require.NoError(t, err)
88+
assert.Equal(t, tt.expected, string(data))
89+
90+
// Test unmarshaling back
91+
var unmarshaled Distribution
92+
err = json.Unmarshal(data, &unmarshaled)
93+
require.NoError(t, err)
94+
assert.Equal(t, dist.VariantKey, unmarshaled.VariantKey)
95+
assert.InDelta(t, dist.Rollout, unmarshaled.Rollout, 0.001)
96+
})
97+
}
98+
}
99+
100+
func TestThresholdRule_ZeroPercentage_YAML(t *testing.T) {
101+
tests := []struct {
102+
name string
103+
percentage float32
104+
value bool
105+
expected string
106+
}{
107+
{
108+
name: "zero percentage with true value",
109+
percentage: 0,
110+
value: true,
111+
expected: "percentage: 0\nvalue: true\n",
112+
},
113+
{
114+
name: "zero float percentage with false value",
115+
percentage: 0.0,
116+
value: false,
117+
expected: "percentage: 0\n",
118+
},
119+
{
120+
name: "non-zero percentage",
121+
percentage: 50.0,
122+
value: true,
123+
expected: "percentage: 50\nvalue: true\n",
124+
},
125+
}
126+
127+
for _, tt := range tests {
128+
t.Run(tt.name, func(t *testing.T) {
129+
threshold := &ThresholdRule{
130+
Percentage: tt.percentage,
131+
Value: tt.value,
132+
}
133+
134+
data, err := yaml.Marshal(threshold)
135+
require.NoError(t, err)
136+
assert.Equal(t, tt.expected, string(data))
137+
138+
// Test unmarshaling back
139+
var unmarshaled ThresholdRule
140+
err = yaml.Unmarshal(data, &unmarshaled)
141+
require.NoError(t, err)
142+
assert.InDelta(t, threshold.Percentage, unmarshaled.Percentage, 0.001)
143+
assert.Equal(t, threshold.Value, unmarshaled.Value)
144+
})
145+
}
146+
}
147+
148+
func TestThresholdRule_ZeroPercentage_JSON(t *testing.T) {
149+
tests := []struct {
150+
name string
151+
percentage float32
152+
value bool
153+
expected string
154+
}{
155+
{
156+
name: "zero percentage with true value",
157+
percentage: 0,
158+
value: true,
159+
expected: `{"percentage":0,"value":true}`,
160+
},
161+
{
162+
name: "zero float percentage with false value",
163+
percentage: 0.0,
164+
value: false,
165+
expected: `{"percentage":0}`,
166+
},
167+
{
168+
name: "non-zero percentage",
169+
percentage: 50.0,
170+
value: true,
171+
expected: `{"percentage":50,"value":true}`,
172+
},
173+
}
174+
175+
for _, tt := range tests {
176+
t.Run(tt.name, func(t *testing.T) {
177+
threshold := &ThresholdRule{
178+
Percentage: tt.percentage,
179+
Value: tt.value,
180+
}
181+
182+
data, err := json.Marshal(threshold)
183+
require.NoError(t, err)
184+
assert.Equal(t, tt.expected, string(data))
185+
186+
// Test unmarshaling back
187+
var unmarshaled ThresholdRule
188+
err = json.Unmarshal(data, &unmarshaled)
189+
require.NoError(t, err)
190+
assert.InDelta(t, threshold.Percentage, unmarshaled.Percentage, 0.001)
191+
assert.Equal(t, threshold.Value, unmarshaled.Value)
192+
})
193+
}
194+
}

0 commit comments

Comments
 (0)