Skip to content

Commit 3d4a795

Browse files
committed
Add Go 1.27 generic context methods
1 parent e552b4d commit 3d4a795

4 files changed

Lines changed: 114 additions & 7 deletions

File tree

context.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -455,11 +455,17 @@ func (c *Context) Cookies() []*http.Cookie {
455455
// Get retrieves data from the context.
456456
// Method returns any(nil) when key does not exist which is different from typed nil (eg. []byte(nil)).
457457
func (c *Context) Get(key string) any {
458+
v, _ := c.get(key)
459+
return v
460+
}
461+
462+
// get retrieves a context value and reports whether its key exists.
463+
func (c *Context) get(key string) (any, bool) {
458464
// Unlock without defer to avoid the deferred-call overhead on this hot path.
459465
c.lock.RLock()
460-
v := c.store[key]
466+
v, ok := c.store[key]
461467
c.lock.RUnlock()
462-
return v
468+
return v, ok
463469
}
464470

465471
// Set saves data in the context.

context_generic.go

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,14 @@ var ErrNonExistentKey = errors.New("non existent key")
1111
// ErrInvalidKeyType is error that is returned when the value is not castable to expected type.
1212
var ErrInvalidKeyType = errors.New("invalid key type")
1313

14-
// ContextGet retrieves a value from the context store or ErrNonExistentKey error the key is missing.
14+
// ContextGet retrieves a value from the context store or ErrNonExistentKey error if the key is missing.
1515
// Returns ErrInvalidKeyType error if the value is not castable to type T.
1616
func ContextGet[T any](c *Context, key string) (T, error) {
17-
c.lock.RLock()
18-
defer c.lock.RUnlock()
17+
return contextValue[T](c, key)
18+
}
1919

20-
val, ok := c.store[key]
20+
func contextValue[T any](c *Context, key string) (T, error) {
21+
val, ok := c.get(key)
2122
if !ok {
2223
var zero T
2324
return zero, ErrNonExistentKey
@@ -35,7 +36,11 @@ func ContextGet[T any](c *Context, key string) (T, error) {
3536
// ContextGetOr retrieves a value from the context store or returns a default value when the key
3637
// is missing. Returns ErrInvalidKeyType error if the value is not castable to type T.
3738
func ContextGetOr[T any](c *Context, key string, defaultValue T) (T, error) {
38-
typed, err := ContextGet[T](c, key)
39+
return contextValueOr(c, key, defaultValue)
40+
}
41+
42+
func contextValueOr[T any](c *Context, key string, defaultValue T) (T, error) {
43+
typed, err := contextValue[T](c, key)
3944
if err == ErrNonExistentKey {
4045
return defaultValue, nil
4146
}

context_generic_go127.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// SPDX-License-Identifier: MIT
2+
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
3+
4+
//go:build go1.27
5+
6+
package echo
7+
8+
// Value retrieves a value from the context store or ErrNonExistentKey error if the key is missing.
9+
// Returns ErrInvalidKeyType error if the value is not castable to type T.
10+
func (c *Context) Value[T any](key string) (T, error) {
11+
return contextValue[T](c, key)
12+
}
13+
14+
// ValueOr retrieves a value from the context store or returns a default value when the key
15+
// is missing. Returns ErrInvalidKeyType error if the value is not castable to type T.
16+
func (c *Context) ValueOr[T any](key string, defaultValue T) (T, error) {
17+
return contextValueOr(c, key, defaultValue)
18+
}

context_generic_go127_test.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// SPDX-License-Identifier: MIT
2+
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
3+
4+
//go:build go1.27
5+
6+
package echo
7+
8+
import (
9+
"testing"
10+
11+
"github.com/stretchr/testify/assert"
12+
)
13+
14+
func TestContextValueOK(t *testing.T) {
15+
c := NewContext(nil, nil)
16+
17+
c.Set("key", int64(123))
18+
19+
v, err := c.Value[int64]("key")
20+
assert.NoError(t, err)
21+
assert.Equal(t, int64(123), v)
22+
}
23+
24+
func TestContextValueNonExistentKey(t *testing.T) {
25+
c := NewContext(nil, nil)
26+
27+
v, err := c.Value[int64]("nope")
28+
assert.ErrorIs(t, err, ErrNonExistentKey)
29+
assert.Equal(t, int64(0), v)
30+
}
31+
32+
func TestContextValueInvalidCast(t *testing.T) {
33+
c := NewContext(nil, nil)
34+
35+
c.Set("key", int64(123))
36+
37+
v, err := c.Value[bool]("key")
38+
assert.ErrorIs(t, err, ErrInvalidKeyType)
39+
assert.False(t, v)
40+
}
41+
42+
func TestContextValueStoredNilHasInvalidType(t *testing.T) {
43+
c := NewContext(nil, nil)
44+
45+
c.Set("key", nil)
46+
47+
v, err := c.Value[any]("key")
48+
assert.ErrorIs(t, err, ErrInvalidKeyType)
49+
assert.Nil(t, v)
50+
}
51+
52+
func TestContextValueOrOK(t *testing.T) {
53+
c := NewContext(nil, nil)
54+
55+
c.Set("key", int64(123))
56+
57+
v, err := c.ValueOr[int64]("key", 999)
58+
assert.NoError(t, err)
59+
assert.Equal(t, int64(123), v)
60+
}
61+
62+
func TestContextValueOrNonExistentKey(t *testing.T) {
63+
c := NewContext(nil, nil)
64+
65+
v, err := c.ValueOr[int64]("nope", 999)
66+
assert.NoError(t, err)
67+
assert.Equal(t, int64(999), v)
68+
}
69+
70+
func TestContextValueOrInvalidCast(t *testing.T) {
71+
c := NewContext(nil, nil)
72+
73+
c.Set("key", int64(123))
74+
75+
v, err := c.ValueOr[float32]("key", float32(999))
76+
assert.ErrorIs(t, err, ErrInvalidKeyType)
77+
assert.Equal(t, float32(0), v)
78+
}

0 commit comments

Comments
 (0)