Skip to content
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

feat: gin.Context GetXxx added support for more go native types #3633

Merged
merged 1 commit into from
Sep 15, 2024
Merged
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
144 changes: 142 additions & 2 deletions context.go
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,31 @@ func (c *Context) GetInt(key string) (i int) {
return
}

// GetInt64 returns the value associated with the key as an integer.
// GetInt8 returns the value associated with the key as an integer 8.
func (c *Context) GetInt8(key string) (i8 int8) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should keep the same logic as the original .Get and returns a (int8, bool) to avoid panicking in case the value if not of the asked type. This comment applies to other methods as well. What do you think?

Copy link
Contributor Author

@CC11001100 CC11001100 Jun 22, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you are right, I just noticed this problem too, but there is a small compatibility problem that seems strange, the methods I added this time are styled by the functions that already existed before:

func (c *Context) GetInt(key string) (i int)

and

func (c *Context) GetInt64(key string) (i64 int64)

The style is kept uniform, and these two functions seem to start from this PR:

https://github.com/gin-gonic/gin/pull/2425

Then other GetXxx functions such as:

func (c *Context) GetString(key string) (s string)
func (c *Context) GetBool(key string) (b bool)

It seems that they all follow the rule of only one return value.

All in all, there seem to be two styles for the Get method on the Context. The first one is:

func (c *Context) Get(key string) (value any, exists bool)

Represented by two return values, the other is based on

func (c *Context) GetInt(key string) (i int)

represented by one return value

image

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, I didn't see that, so what you've done makes sense.

But for the record, I guess we could leverage Go generic with something like this:

func Get[T any](c *gin.Context, key string) (r T) {
	if val, ok := c.Get(key); ok && val != nil {
		r, _ = val.(T)
	}
	return
}

Sure we can't add this on the Context type because Go doesn't currently support method level generic types yet.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you have a point, the best way is to add a generic method to Context, although I don't think Golang will support this feature any time soon, but I believe it will be supported in the future, it is a mature language should have a feature. some proposal about it:
golang/go#49085

However, in the absence of generics, enumeration method can only be used. Although there is a lot of repetitive work, using a separate method can solve this problem, but it will be a problem to promote the use of this function. Most people use the method in Context and then view it with dot. So I think it is necessary to add these methods to Context for now, as a transition that will remove in the future when Golang supports Struct generic methods.

if val, ok := c.Get(key); ok && val != nil {
i8, _ = val.(int8)
}
return
}

// GetInt16 returns the value associated with the key as an integer 16.
func (c *Context) GetInt16(key string) (i16 int16) {
if val, ok := c.Get(key); ok && val != nil {
i16, _ = val.(int16)
}
return
}

// GetInt32 returns the value associated with the key as an integer 32.
func (c *Context) GetInt32(key string) (i32 int32) {
if val, ok := c.Get(key); ok && val != nil {
i32, _ = val.(int32)
}
return
}

// GetInt64 returns the value associated with the key as an integer 64.
func (c *Context) GetInt64(key string) (i64 int64) {
if val, ok := c.Get(key); ok && val != nil {
i64, _ = val.(int64)
Expand All @@ -313,14 +337,46 @@ func (c *Context) GetUint(key string) (ui uint) {
return
}

// GetUint64 returns the value associated with the key as an unsigned integer.
// GetUint8 returns the value associated with the key as an unsigned integer 8.
func (c *Context) GetUint8(key string) (ui8 uint8) {
if val, ok := c.Get(key); ok && val != nil {
ui8, _ = val.(uint8)
}
return
}

// GetUint16 returns the value associated with the key as an unsigned integer 16.
func (c *Context) GetUint16(key string) (ui16 uint16) {
if val, ok := c.Get(key); ok && val != nil {
ui16, _ = val.(uint16)
}
return
}

// GetUint32 returns the value associated with the key as an unsigned integer 32.
func (c *Context) GetUint32(key string) (ui32 uint32) {
if val, ok := c.Get(key); ok && val != nil {
ui32, _ = val.(uint32)
}
return
}

// GetUint64 returns the value associated with the key as an unsigned integer 64.
func (c *Context) GetUint64(key string) (ui64 uint64) {
if val, ok := c.Get(key); ok && val != nil {
ui64, _ = val.(uint64)
}
return
}

// GetFloat32 returns the value associated with the key as a float32.
func (c *Context) GetFloat32(key string) (f32 float32) {
if val, ok := c.Get(key); ok && val != nil {
f32, _ = val.(float32)
}
return
}

// GetFloat64 returns the value associated with the key as a float64.
func (c *Context) GetFloat64(key string) (f64 float64) {
if val, ok := c.Get(key); ok && val != nil {
Expand All @@ -345,6 +401,90 @@ func (c *Context) GetDuration(key string) (d time.Duration) {
return
}

func (c *Context) GetIntSlice(key string) (is []int) {
if val, ok := c.Get(key); ok && val != nil {
is, _ = val.([]int)
}
return
}

func (c *Context) GetInt8Slice(key string) (i8s []int8) {
if val, ok := c.Get(key); ok && val != nil {
i8s, _ = val.([]int8)
}
return
}

func (c *Context) GetInt16Slice(key string) (i16s []int16) {
if val, ok := c.Get(key); ok && val != nil {
i16s, _ = val.([]int16)
}
return
}

func (c *Context) GetInt32Slice(key string) (i32s []int32) {
if val, ok := c.Get(key); ok && val != nil {
i32s, _ = val.([]int32)
}
return
}

func (c *Context) GetInt64Slice(key string) (i64s []int64) {
if val, ok := c.Get(key); ok && val != nil {
i64s, _ = val.([]int64)
}
return
}

func (c *Context) GetUintSlice(key string) (uis []uint) {
if val, ok := c.Get(key); ok && val != nil {
uis, _ = val.([]uint)
}
return
}

func (c *Context) GetUint8Slice(key string) (ui8s []uint8) {
if val, ok := c.Get(key); ok && val != nil {
ui8s, _ = val.([]uint8)
}
return
}

func (c *Context) GetUint16Slice(key string) (ui16s []uint16) {
if val, ok := c.Get(key); ok && val != nil {
ui16s, _ = val.([]uint16)
}
return
}

func (c *Context) GetUint32Slice(key string) (ui32s []uint32) {
if val, ok := c.Get(key); ok && val != nil {
ui32s, _ = val.([]uint32)
}
return
}

func (c *Context) GetUint64Slice(key string) (ui64s []uint64) {
if val, ok := c.Get(key); ok && val != nil {
ui64s, _ = val.([]uint64)
}
return
}

func (c *Context) GetFloat32Slice(key string) (f32s []float32) {
if val, ok := c.Get(key); ok && val != nil {
f32s, _ = val.([]float32)
}
return
}

func (c *Context) GetFloat64Slice(key string) (f64s []float64) {
if val, ok := c.Get(key); ok && val != nil {
f64s, _ = val.([]float64)
}
return
}

// GetStringSlice returns the value associated with the key as a slice of strings.
func (c *Context) GetStringSlice(key string) (ss []string) {
if val, ok := c.Get(key); ok && val != nil {
Expand Down
152 changes: 152 additions & 0 deletions context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,30 @@ func TestContextGetInt(t *testing.T) {
assert.Equal(t, 1, c.GetInt("int"))
}

func TestContextGetInt8(t *testing.T) {
c, _ := CreateTestContext(httptest.NewRecorder())
key := "int8"
value := int8(0x7F)
c.Set(key, value)
assert.Equal(t, value, c.GetInt8(key))
}

func TestContextGetInt16(t *testing.T) {
c, _ := CreateTestContext(httptest.NewRecorder())
key := "int16"
value := int16(0x7FFF)
c.Set(key, value)
assert.Equal(t, value, c.GetInt16(key))
}

func TestContextGetInt32(t *testing.T) {
c, _ := CreateTestContext(httptest.NewRecorder())
key := "int32"
value := int32(0x7FFFFFFF)
c.Set(key, value)
assert.Equal(t, value, c.GetInt32(key))
}

func TestContextGetInt64(t *testing.T) {
c, _ := CreateTestContext(httptest.NewRecorder())
c.Set("int64", int64(42424242424242))
Expand All @@ -256,12 +280,44 @@ func TestContextGetUint(t *testing.T) {
assert.Equal(t, uint(1), c.GetUint("uint"))
}

func TestContextGetUint8(t *testing.T) {
c, _ := CreateTestContext(httptest.NewRecorder())
key := "uint8"
value := uint8(0xFF)
c.Set(key, value)
assert.Equal(t, value, c.GetUint8(key))
}

func TestContextGetUint16(t *testing.T) {
c, _ := CreateTestContext(httptest.NewRecorder())
key := "uint16"
value := uint16(0xFFFF)
c.Set(key, value)
assert.Equal(t, value, c.GetUint16(key))
}

func TestContextGetUint32(t *testing.T) {
c, _ := CreateTestContext(httptest.NewRecorder())
key := "uint32"
value := uint32(0xFFFFFFFF)
c.Set(key, value)
assert.Equal(t, value, c.GetUint32(key))
}

func TestContextGetUint64(t *testing.T) {
c, _ := CreateTestContext(httptest.NewRecorder())
c.Set("uint64", uint64(18446744073709551615))
assert.Equal(t, uint64(18446744073709551615), c.GetUint64("uint64"))
}

func TestContextGetFloat32(t *testing.T) {
c, _ := CreateTestContext(httptest.NewRecorder())
key := "float32"
value := float32(3.14)
c.Set(key, value)
assert.Equal(t, value, c.GetFloat32(key))
}

func TestContextGetFloat64(t *testing.T) {
c, _ := CreateTestContext(httptest.NewRecorder())
c.Set("float64", 4.2)
Expand All @@ -281,6 +337,102 @@ func TestContextGetDuration(t *testing.T) {
assert.Equal(t, time.Second, c.GetDuration("duration"))
}

func TestContextGetIntSlice(t *testing.T) {
c, _ := CreateTestContext(httptest.NewRecorder())
key := "int-slice"
value := []int{1, 2}
c.Set(key, value)
assert.Equal(t, value, c.GetIntSlice(key))
}

func TestContextGetInt8Slice(t *testing.T) {
c, _ := CreateTestContext(httptest.NewRecorder())
key := "int8-slice"
value := []int8{1, 2}
c.Set(key, value)
assert.Equal(t, value, c.GetInt8Slice(key))
}

func TestContextGetInt16Slice(t *testing.T) {
c, _ := CreateTestContext(httptest.NewRecorder())
key := "int16-slice"
value := []int16{1, 2}
c.Set(key, value)
assert.Equal(t, value, c.GetInt16Slice(key))
}

func TestContextGetInt32Slice(t *testing.T) {
c, _ := CreateTestContext(httptest.NewRecorder())
key := "int32-slice"
value := []int32{1, 2}
c.Set(key, value)
assert.Equal(t, value, c.GetInt32Slice(key))
}

func TestContextGetInt64Slice(t *testing.T) {
c, _ := CreateTestContext(httptest.NewRecorder())
key := "int64-slice"
value := []int64{1, 2}
c.Set(key, value)
assert.Equal(t, value, c.GetInt64Slice(key))
}

func TestContextGetUintSlice(t *testing.T) {
c, _ := CreateTestContext(httptest.NewRecorder())
key := "uint-slice"
value := []uint{1, 2}
c.Set(key, value)
assert.Equal(t, value, c.GetUintSlice(key))
}

func TestContextGetUint8Slice(t *testing.T) {
c, _ := CreateTestContext(httptest.NewRecorder())
key := "uint8-slice"
value := []uint8{1, 2}
c.Set(key, value)
assert.Equal(t, value, c.GetUint8Slice(key))
}

func TestContextGetUint16Slice(t *testing.T) {
c, _ := CreateTestContext(httptest.NewRecorder())
key := "uint16-slice"
value := []uint16{1, 2}
c.Set(key, value)
assert.Equal(t, value, c.GetUint16Slice(key))
}

func TestContextGetUint32Slice(t *testing.T) {
c, _ := CreateTestContext(httptest.NewRecorder())
key := "uint32-slice"
value := []uint32{1, 2}
c.Set(key, value)
assert.Equal(t, value, c.GetUint32Slice(key))
}

func TestContextGetUint64Slice(t *testing.T) {
c, _ := CreateTestContext(httptest.NewRecorder())
key := "uint64-slice"
value := []uint64{1, 2}
c.Set(key, value)
assert.Equal(t, value, c.GetUint64Slice(key))
}

func TestContextGetFloat32Slice(t *testing.T) {
c, _ := CreateTestContext(httptest.NewRecorder())
key := "float32-slice"
value := []float32{1, 2}
c.Set(key, value)
assert.Equal(t, value, c.GetFloat32Slice(key))
}

func TestContextGetFloat64Slice(t *testing.T) {
c, _ := CreateTestContext(httptest.NewRecorder())
key := "float64-slice"
value := []float64{1, 2}
c.Set(key, value)
assert.Equal(t, value, c.GetFloat64Slice(key))
}

func TestContextGetStringSlice(t *testing.T) {
c, _ := CreateTestContext(httptest.NewRecorder())
c.Set("slice", []string{"foo"})
Expand Down