初始提交: Gitea 项目代码

This commit is contained in:
root
2026-05-30 22:47:36 +08:00
commit f288f76350
6116 changed files with 776822 additions and 0 deletions
+83
View File
@@ -0,0 +1,83 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package optional
import "strconv"
// Option is a generic type that can hold a value of type T or be empty (None).
//
// It must use the slice type to work with "chi" form values binding:
// * non-existing value are represented as an empty slice (None)
// * existing value is represented as a slice with one element (Some)
// * multiple values are represented as a slice with multiple elements (Some), the Value is the first element (not well-defined in this case)
type Option[T any] []T
func None[T any]() Option[T] {
return nil
}
func Some[T any](v T) Option[T] {
return Option[T]{v}
}
func FromPtr[T any](v *T) Option[T] {
if v == nil {
return None[T]()
}
return Some(*v)
}
func FromMapLookup[K comparable, V any](m map[K]V, k K) Option[V] {
if v, ok := m[k]; ok {
return Some(v)
}
return None[V]()
}
func FromNonDefault[T comparable](v T) Option[T] {
var zero T
if v == zero {
return None[T]()
}
return Some(v)
}
func (o Option[T]) Has() bool {
return o != nil
}
func (o Option[T]) Value() T {
var zero T
return o.ValueOrDefault(zero)
}
func (o Option[T]) ValueOrDefault(v T) T {
if o.Has() {
return o[0]
}
return v
}
// ParseBool get the corresponding optional.Option[bool] of a string using strconv.ParseBool
func ParseBool(s string) Option[bool] {
v, e := strconv.ParseBool(s)
if e != nil {
return None[bool]()
}
return Some(v)
}
func AssignPtrValue[T comparable](changed *bool, target, src *T) {
if src != nil && *src != *target {
*target = *src
*changed = true
}
}
func AssignPtrString[TO, FROM ~string](changed *bool, target *TO, src *FROM) {
if src != nil && string(*src) != string(*target) {
*target = TO(*src)
*changed = true
}
}
+78
View File
@@ -0,0 +1,78 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package optional_test
import (
"testing"
"gitea.dev/modules/optional"
"github.com/stretchr/testify/assert"
)
func TestOption(t *testing.T) {
var uninitialized optional.Option[int]
assert.False(t, uninitialized.Has())
assert.Equal(t, int(0), uninitialized.Value())
assert.Equal(t, int(1), uninitialized.ValueOrDefault(1))
none := optional.None[int]()
assert.False(t, none.Has())
assert.Equal(t, int(0), none.Value())
assert.Equal(t, int(1), none.ValueOrDefault(1))
some := optional.Some(1)
assert.True(t, some.Has())
assert.Equal(t, int(1), some.Value())
assert.Equal(t, int(1), some.ValueOrDefault(2))
noneBool := optional.None[bool]()
assert.False(t, noneBool.Has())
assert.False(t, noneBool.Value())
assert.True(t, noneBool.ValueOrDefault(true))
someBool := optional.Some(true)
assert.True(t, someBool.Has())
assert.True(t, someBool.Value())
assert.True(t, someBool.ValueOrDefault(false))
var ptr *int
assert.False(t, optional.FromPtr(ptr).Has())
int1 := 1
opt1 := optional.FromPtr(&int1)
assert.True(t, opt1.Has())
assert.Equal(t, int(1), opt1.Value())
assert.False(t, optional.FromNonDefault("").Has())
opt2 := optional.FromNonDefault("test")
assert.True(t, opt2.Has())
assert.Equal(t, "test", opt2.Value())
assert.False(t, optional.FromNonDefault(0).Has())
opt3 := optional.FromNonDefault(1)
assert.True(t, opt3.Has())
assert.Equal(t, int(1), opt3.Value())
opt4 := optional.FromMapLookup(map[string]int{"a": 1}, "a")
assert.True(t, opt4.Has())
assert.Equal(t, 1, opt4.Value())
opt4 = optional.FromMapLookup(map[string]int{"a": 1}, "b")
assert.False(t, opt4.Has())
}
func Test_ParseBool(t *testing.T) {
assert.Equal(t, optional.None[bool](), optional.ParseBool(""))
assert.Equal(t, optional.None[bool](), optional.ParseBool("x"))
assert.Equal(t, optional.Some(false), optional.ParseBool("0"))
assert.Equal(t, optional.Some(false), optional.ParseBool("f"))
assert.Equal(t, optional.Some(false), optional.ParseBool("False"))
assert.Equal(t, optional.Some(true), optional.ParseBool("1"))
assert.Equal(t, optional.Some(true), optional.ParseBool("t"))
assert.Equal(t, optional.Some(true), optional.ParseBool("True"))
}
+46
View File
@@ -0,0 +1,46 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package optional
import (
"gitea.dev/modules/json"
"go.yaml.in/yaml/v4"
)
func (o *Option[T]) UnmarshalJSON(data []byte) error {
var v *T
if err := json.Unmarshal(data, &v); err != nil {
return err
}
*o = FromPtr(v)
return nil
}
func (o Option[T]) MarshalJSON() ([]byte, error) {
if !o.Has() {
return []byte("null"), nil
}
return json.Marshal(o.Value())
}
func (o *Option[T]) UnmarshalYAML(value *yaml.Node) error {
var v *T
if err := value.Decode(&v); err != nil {
return err
}
*o = FromPtr(v)
return nil
}
func (o Option[T]) MarshalYAML() (any, error) {
if !o.Has() {
return nil, nil //nolint:nilnil // return nil to indicate no value to marshal
}
value := new(yaml.Node)
err := value.Encode(o.Value())
return value, err
}
+195
View File
@@ -0,0 +1,195 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package optional_test
import (
std_json "encoding/json" //nolint:depguard // for testing purpose
"testing"
"gitea.dev/modules/json"
"gitea.dev/modules/optional"
"github.com/stretchr/testify/assert"
"go.yaml.in/yaml/v4"
)
type testSerializationStruct struct {
NormalString string `json:"normal_string" yaml:"normal_string"`
NormalBool bool `json:"normal_bool" yaml:"normal_bool"`
OptBool optional.Option[bool] `json:"optional_bool,omitempty" yaml:"optional_bool,omitempty"`
// It causes an undefined behavior: should the "omitempty" tag only omit "null", or also the empty string?
// The behavior is inconsistent between json and v2 packages, and there is no such use case in Gitea.
// If anyone really needs it, they can use json.MarshalKeepOptionalEmpty to revert the v1 behavior
OptString optional.Option[string] `json:"optional_string,omitempty" yaml:"optional_string,omitempty"`
OptTwoBool optional.Option[bool] `json:"optional_two_bool" yaml:"optional_two_bool"`
OptTwoString optional.Option[string] `json:"optional_two_string" yaml:"optional_two_string"`
}
func TestOptionalToJson(t *testing.T) {
tests := []struct {
name string
obj *testSerializationStruct
want string
}{
{
name: "empty",
obj: new(testSerializationStruct),
want: `{"normal_string":"","normal_bool":false,"optional_two_bool":null,"optional_two_string":null}`,
},
{
name: "some",
obj: &testSerializationStruct{
NormalString: "a string",
NormalBool: true,
OptBool: optional.Some(false),
OptString: optional.Some(""),
OptTwoBool: optional.None[bool](),
OptTwoString: optional.None[string](),
},
want: `{"normal_string":"a string","normal_bool":true,"optional_bool":false,"optional_string":"","optional_two_bool":null,"optional_two_string":null}`,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
b, err := json.MarshalKeepOptionalEmpty(tc.obj)
assert.NoError(t, err)
assert.Equal(t, tc.want, string(b), "gitea json module returned unexpected")
b, err = std_json.Marshal(tc.obj)
assert.NoError(t, err)
assert.Equal(t, tc.want, string(b), "std json module returned unexpected")
})
}
}
func TestOptionalFromJson(t *testing.T) {
tests := []struct {
name string
data string
want testSerializationStruct
}{
{
name: "empty",
data: `{}`,
want: testSerializationStruct{
NormalString: "",
},
},
{
name: "some",
data: `{"normal_string":"a string","normal_bool":true,"optional_bool":false,"optional_string":"","optional_two_bool":null,"optional_two_string":null}`,
want: testSerializationStruct{
NormalString: "a string",
NormalBool: true,
OptBool: optional.Some(false),
OptString: optional.Some(""),
},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
var obj1 testSerializationStruct
err := json.Unmarshal([]byte(tc.data), &obj1)
assert.NoError(t, err)
assert.Equal(t, tc.want, obj1, "gitea json module returned unexpected")
var obj2 testSerializationStruct
err = std_json.Unmarshal([]byte(tc.data), &obj2)
assert.NoError(t, err)
assert.Equal(t, tc.want, obj2, "std json module returned unexpected")
})
}
}
func TestOptionalToYaml(t *testing.T) {
tests := []struct {
name string
obj *testSerializationStruct
want string
}{
{
name: "empty",
obj: new(testSerializationStruct),
want: `normal_string: ""
normal_bool: false
optional_two_bool: null
optional_two_string: null
`,
},
{
name: "some",
obj: &testSerializationStruct{
NormalString: "a string",
NormalBool: true,
OptBool: optional.Some(false),
OptString: optional.Some(""),
},
want: `normal_string: a string
normal_bool: true
optional_bool: false
optional_string: ""
optional_two_bool: null
optional_two_string: null
`,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
b, err := yaml.Marshal(tc.obj)
assert.NoError(t, err)
assert.Equal(t, tc.want, string(b), "yaml module returned unexpected")
})
}
}
func TestOptionalFromYaml(t *testing.T) {
tests := []struct {
name string
data string
want testSerializationStruct
}{
{
name: "empty",
data: ``,
want: testSerializationStruct{},
},
{
name: "empty but init",
data: `normal_string: ""
normal_bool: false
optional_bool:
optional_two_bool:
optional_two_string:
`,
want: testSerializationStruct{},
},
{
name: "some",
data: `
normal_string: a string
normal_bool: true
optional_bool: false
optional_string: ""
optional_two_bool: null
optional_two_string: null
`,
want: testSerializationStruct{
NormalString: "a string",
NormalBool: true,
OptBool: optional.Some(false),
OptString: optional.Some(""),
},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
var obj testSerializationStruct
err := yaml.Unmarshal([]byte(tc.data), &obj)
assert.NoError(t, err)
assert.Equal(t, tc.want, obj, "yaml module returned unexpected")
})
}
}