Skip to content

Add optional.ExtractValue() to be able to handle Option type within interface{} value #29794

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions modules/optional/option.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

package optional

import "reflect"

type Option[T any] []T

func None[T any]() Option[T] {
Expand Down Expand Up @@ -43,3 +45,29 @@ func (o Option[T]) ValueOrDefault(v T) T {
}
return v
}

// ExtractValue return value or nil and bool if object was an Optional
// it should only be used if you already have to deal with interface{} values
// and expect an Option type within it.
func ExtractValue(obj any) (any, bool) {
rt := reflect.TypeOf(obj)
if rt.Kind() != reflect.Slice {
return nil, false
}

type hasHasFunc interface {
Has() bool
}
if hasObj, ok := obj.(hasHasFunc); !ok {
return nil, false
} else if !hasObj.Has() {
return nil, true
}

rv := reflect.ValueOf(obj)
if rv.Len() != 1 {
// it's still false as optional.Option[T] types would have reported with hasObj.Has() that it is empty
return nil, false
}
return rv.Index(0).Interface(), true
}
39 changes: 39 additions & 0 deletions modules/optional/option_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,42 @@ func TestOption(t *testing.T) {
assert.True(t, opt3.Has())
assert.Equal(t, int(1), opt3.Value())
}

func TestExtractValue(t *testing.T) {
val, ok := optional.ExtractValue("aaaa")
assert.False(t, ok)
assert.Nil(t, val)

val, ok = optional.ExtractValue(optional.Some("aaaa"))
assert.True(t, ok)
if assert.NotNil(t, val) {
val, ok := val.(string)
assert.True(t, ok)
assert.EqualValues(t, "aaaa", val)
}

val, ok = optional.ExtractValue(optional.None[float64]())
assert.True(t, ok)
assert.Nil(t, val)

val, ok = optional.ExtractValue(&fakeHas{})
assert.False(t, ok)
assert.Nil(t, val)

wrongType := make(fakeHas2, 0, 1)
val, ok = optional.ExtractValue(wrongType)
assert.False(t, ok)
assert.Nil(t, val)
}

type fakeHas struct{}

func (fakeHas) Has() bool {
return true
}

type fakeHas2 []string

func (fakeHas2) Has() bool {
return true
}
14 changes: 12 additions & 2 deletions services/context/pagination.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"net/url"
"strings"

"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/paginator"
)

Expand All @@ -28,11 +29,20 @@ func NewPagination(total, pagingNum, current, numPages int) *Pagination {

// AddParam adds a value from context identified by ctxKey as link param under a given paramKey
func (p *Pagination) AddParam(ctx *Context, paramKey, ctxKey string) {
_, exists := ctx.Data[ctxKey]
obj, exists := ctx.Data[ctxKey]
if !exists {
return
}
paramData := fmt.Sprintf("%v", ctx.Data[ctxKey]) // cast any to string
// we check if the value in the context is an optional.Option type and either skip if it contains None
// or unwrap it if it is Some
if optVal, is := optional.ExtractValue(obj); is {
if optVal == nil {
// optional value is currently None
return
}
obj = optVal
}
paramData := fmt.Sprintf("%v", obj) // cast any to string
urlParam := fmt.Sprintf("%s=%v", url.QueryEscape(paramKey), url.QueryEscape(paramData))
p.urlParams = append(p.urlParams, urlParam)
}
Expand Down