Skip to content

Commit 53c7d89

Browse files
6543KN4CK3R
andauthored
Make optional.Option[T] type serializable (#29282)
make the generic `Option` type de-/serializable for json and yaml --------- Co-authored-by: KN4CK3R <admin@oldschoolhack.me>
1 parent 12d233f commit 53c7d89

File tree

3 files changed

+248
-10
lines changed

3 files changed

+248
-10
lines changed

modules/optional/option_test.go

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,49 @@
11
// Copyright 2024 The Gitea Authors. All rights reserved.
22
// SPDX-License-Identifier: MIT
33

4-
package optional
4+
package optional_test
55

66
import (
77
"testing"
88

9+
"code.gitea.io/gitea/modules/optional"
10+
911
"github.com/stretchr/testify/assert"
1012
)
1113

1214
func TestOption(t *testing.T) {
13-
var uninitialized Option[int]
15+
var uninitialized optional.Option[int]
1416
assert.False(t, uninitialized.Has())
1517
assert.Equal(t, int(0), uninitialized.Value())
1618
assert.Equal(t, int(1), uninitialized.ValueOrDefault(1))
1719

18-
none := None[int]()
20+
none := optional.None[int]()
1921
assert.False(t, none.Has())
2022
assert.Equal(t, int(0), none.Value())
2123
assert.Equal(t, int(1), none.ValueOrDefault(1))
2224

23-
some := Some[int](1)
25+
some := optional.Some[int](1)
2426
assert.True(t, some.Has())
2527
assert.Equal(t, int(1), some.Value())
2628
assert.Equal(t, int(1), some.ValueOrDefault(2))
2729

2830
var ptr *int
29-
assert.False(t, FromPtr(ptr).Has())
31+
assert.False(t, optional.FromPtr(ptr).Has())
3032

3133
int1 := 1
32-
opt1 := FromPtr(&int1)
34+
opt1 := optional.FromPtr(&int1)
3335
assert.True(t, opt1.Has())
3436
assert.Equal(t, int(1), opt1.Value())
3537

36-
assert.False(t, FromNonDefault("").Has())
38+
assert.False(t, optional.FromNonDefault("").Has())
3739

38-
opt2 := FromNonDefault("test")
40+
opt2 := optional.FromNonDefault("test")
3941
assert.True(t, opt2.Has())
4042
assert.Equal(t, "test", opt2.Value())
4143

42-
assert.False(t, FromNonDefault(0).Has())
44+
assert.False(t, optional.FromNonDefault(0).Has())
4345

44-
opt3 := FromNonDefault(1)
46+
opt3 := optional.FromNonDefault(1)
4547
assert.True(t, opt3.Has())
4648
assert.Equal(t, int(1), opt3.Value())
4749
}

modules/optional/serialization.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Copyright 2024 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package optional
5+
6+
import (
7+
"code.gitea.io/gitea/modules/json"
8+
9+
"gopkg.in/yaml.v3"
10+
)
11+
12+
func (o *Option[T]) UnmarshalJSON(data []byte) error {
13+
var v *T
14+
if err := json.Unmarshal(data, &v); err != nil {
15+
return err
16+
}
17+
*o = FromPtr(v)
18+
return nil
19+
}
20+
21+
func (o Option[T]) MarshalJSON() ([]byte, error) {
22+
if !o.Has() {
23+
return []byte("null"), nil
24+
}
25+
26+
return json.Marshal(o.Value())
27+
}
28+
29+
func (o *Option[T]) UnmarshalYAML(value *yaml.Node) error {
30+
var v *T
31+
if err := value.Decode(&v); err != nil {
32+
return err
33+
}
34+
*o = FromPtr(v)
35+
return nil
36+
}
37+
38+
func (o Option[T]) MarshalYAML() (interface{}, error) {
39+
if !o.Has() {
40+
return nil, nil
41+
}
42+
43+
value := new(yaml.Node)
44+
err := value.Encode(o.Value())
45+
return value, err
46+
}
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
// Copyright 2024 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package optional_test
5+
6+
import (
7+
std_json "encoding/json" //nolint:depguard
8+
"testing"
9+
10+
"code.gitea.io/gitea/modules/json"
11+
"code.gitea.io/gitea/modules/optional"
12+
13+
"github.com/stretchr/testify/assert"
14+
"gopkg.in/yaml.v3"
15+
)
16+
17+
type testSerializationStruct struct {
18+
NormalString string `json:"normal_string" yaml:"normal_string"`
19+
NormalBool bool `json:"normal_bool" yaml:"normal_bool"`
20+
OptBool optional.Option[bool] `json:"optional_bool,omitempty" yaml:"optional_bool,omitempty"`
21+
OptString optional.Option[string] `json:"optional_string,omitempty" yaml:"optional_string,omitempty"`
22+
OptTwoBool optional.Option[bool] `json:"optional_two_bool" yaml:"optional_two_bool"`
23+
OptTwoString optional.Option[string] `json:"optional_twostring" yaml:"optional_two_string"`
24+
}
25+
26+
func TestOptionalToJson(t *testing.T) {
27+
tests := []struct {
28+
name string
29+
obj *testSerializationStruct
30+
want string
31+
}{
32+
{
33+
name: "empty",
34+
obj: new(testSerializationStruct),
35+
want: `{"normal_string":"","normal_bool":false,"optional_two_bool":null,"optional_twostring":null}`,
36+
},
37+
{
38+
name: "some",
39+
obj: &testSerializationStruct{
40+
NormalString: "a string",
41+
NormalBool: true,
42+
OptBool: optional.Some(false),
43+
OptString: optional.Some(""),
44+
OptTwoBool: optional.None[bool](),
45+
OptTwoString: optional.None[string](),
46+
},
47+
want: `{"normal_string":"a string","normal_bool":true,"optional_bool":false,"optional_string":"","optional_two_bool":null,"optional_twostring":null}`,
48+
},
49+
}
50+
for _, tc := range tests {
51+
t.Run(tc.name, func(t *testing.T) {
52+
b, err := json.Marshal(tc.obj)
53+
assert.NoError(t, err)
54+
assert.EqualValues(t, tc.want, string(b), "gitea json module returned unexpected")
55+
56+
b, err = std_json.Marshal(tc.obj)
57+
assert.NoError(t, err)
58+
assert.EqualValues(t, tc.want, string(b), "std json module returned unexpected")
59+
})
60+
}
61+
}
62+
63+
func TestOptionalFromJson(t *testing.T) {
64+
tests := []struct {
65+
name string
66+
data string
67+
want testSerializationStruct
68+
}{
69+
{
70+
name: "empty",
71+
data: `{}`,
72+
want: testSerializationStruct{
73+
NormalString: "",
74+
},
75+
},
76+
{
77+
name: "some",
78+
data: `{"normal_string":"a string","normal_bool":true,"optional_bool":false,"optional_string":"","optional_two_bool":null,"optional_twostring":null}`,
79+
want: testSerializationStruct{
80+
NormalString: "a string",
81+
NormalBool: true,
82+
OptBool: optional.Some(false),
83+
OptString: optional.Some(""),
84+
},
85+
},
86+
}
87+
for _, tc := range tests {
88+
t.Run(tc.name, func(t *testing.T) {
89+
var obj1 testSerializationStruct
90+
err := json.Unmarshal([]byte(tc.data), &obj1)
91+
assert.NoError(t, err)
92+
assert.EqualValues(t, tc.want, obj1, "gitea json module returned unexpected")
93+
94+
var obj2 testSerializationStruct
95+
err = std_json.Unmarshal([]byte(tc.data), &obj2)
96+
assert.NoError(t, err)
97+
assert.EqualValues(t, tc.want, obj2, "std json module returned unexpected")
98+
})
99+
}
100+
}
101+
102+
func TestOptionalToYaml(t *testing.T) {
103+
tests := []struct {
104+
name string
105+
obj *testSerializationStruct
106+
want string
107+
}{
108+
{
109+
name: "empty",
110+
obj: new(testSerializationStruct),
111+
want: `normal_string: ""
112+
normal_bool: false
113+
optional_two_bool: null
114+
optional_two_string: null
115+
`,
116+
},
117+
{
118+
name: "some",
119+
obj: &testSerializationStruct{
120+
NormalString: "a string",
121+
NormalBool: true,
122+
OptBool: optional.Some(false),
123+
OptString: optional.Some(""),
124+
},
125+
want: `normal_string: a string
126+
normal_bool: true
127+
optional_bool: false
128+
optional_string: ""
129+
optional_two_bool: null
130+
optional_two_string: null
131+
`,
132+
},
133+
}
134+
for _, tc := range tests {
135+
t.Run(tc.name, func(t *testing.T) {
136+
b, err := yaml.Marshal(tc.obj)
137+
assert.NoError(t, err)
138+
assert.EqualValues(t, tc.want, string(b), "yaml module returned unexpected")
139+
})
140+
}
141+
}
142+
143+
func TestOptionalFromYaml(t *testing.T) {
144+
tests := []struct {
145+
name string
146+
data string
147+
want testSerializationStruct
148+
}{
149+
{
150+
name: "empty",
151+
data: ``,
152+
want: testSerializationStruct{},
153+
},
154+
{
155+
name: "empty but init",
156+
data: `normal_string: ""
157+
normal_bool: false
158+
optional_bool:
159+
optional_two_bool:
160+
optional_two_string:
161+
`,
162+
want: testSerializationStruct{},
163+
},
164+
{
165+
name: "some",
166+
data: `
167+
normal_string: a string
168+
normal_bool: true
169+
optional_bool: false
170+
optional_string: ""
171+
optional_two_bool: null
172+
optional_twostring: null
173+
`,
174+
want: testSerializationStruct{
175+
NormalString: "a string",
176+
NormalBool: true,
177+
OptBool: optional.Some(false),
178+
OptString: optional.Some(""),
179+
},
180+
},
181+
}
182+
for _, tc := range tests {
183+
t.Run(tc.name, func(t *testing.T) {
184+
var obj testSerializationStruct
185+
err := yaml.Unmarshal([]byte(tc.data), &obj)
186+
assert.NoError(t, err)
187+
assert.EqualValues(t, tc.want, obj, "yaml module returned unexpected")
188+
})
189+
}
190+
}

0 commit comments

Comments
 (0)