Skip to content
Open
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
10 changes: 8 additions & 2 deletions context.go
Original file line number Diff line number Diff line change
Expand Up @@ -455,11 +455,17 @@ func (c *Context) Cookies() []*http.Cookie {
// Get retrieves data from the context.
// Method returns any(nil) when key does not exist which is different from typed nil (eg. []byte(nil)).
func (c *Context) Get(key string) any {
v, _ := c.get(key)
return v
}

// get retrieves a context value and reports whether its key exists.
func (c *Context) get(key string) (any, bool) {
// Unlock without defer to avoid the deferred-call overhead on this hot path.
c.lock.RLock()
v := c.store[key]
v, ok := c.store[key]
c.lock.RUnlock()
return v
return v, ok
}

// Set saves data in the context.
Expand Down
15 changes: 10 additions & 5 deletions context_generic.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@ var ErrNonExistentKey = errors.New("non existent key")
// ErrInvalidKeyType is error that is returned when the value is not castable to expected type.
var ErrInvalidKeyType = errors.New("invalid key type")

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

val, ok := c.store[key]
func contextValue[T any](c *Context, key string) (T, error) {
val, ok := c.get(key)
if !ok {
var zero T
return zero, ErrNonExistentKey
Expand All @@ -35,7 +36,11 @@ func ContextGet[T any](c *Context, key string) (T, error) {
// ContextGetOr retrieves a value from the context store or returns a default value when the key
// is missing. Returns ErrInvalidKeyType error if the value is not castable to type T.
func ContextGetOr[T any](c *Context, key string, defaultValue T) (T, error) {
typed, err := ContextGet[T](c, key)
return contextValueOr(c, key, defaultValue)
}

func contextValueOr[T any](c *Context, key string, defaultValue T) (T, error) {
typed, err := contextValue[T](c, key)
if err == ErrNonExistentKey {
return defaultValue, nil
}
Expand Down
18 changes: 18 additions & 0 deletions context_generic_go127.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors

//go:build go1.27

package echo

// Value retrieves a value from the context store or ErrNonExistentKey error if the key is missing.
// Returns ErrInvalidKeyType error if the value is not castable to type T.
func (c *Context) Value[T any](key string) (T, error) {
return contextValue[T](c, key)
}

// ValueOr retrieves a value from the context store or returns a default value when the key
// is missing. Returns ErrInvalidKeyType error if the value is not castable to type T.
func (c *Context) ValueOr[T any](key string, defaultValue T) (T, error) {
return contextValueOr(c, key, defaultValue)
}
78 changes: 78 additions & 0 deletions context_generic_go127_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors

//go:build go1.27

package echo

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestContextValueOK(t *testing.T) {
c := NewContext(nil, nil)

c.Set("key", int64(123))

v, err := c.Value[int64]("key")
assert.NoError(t, err)
assert.Equal(t, int64(123), v)
}

func TestContextValueNonExistentKey(t *testing.T) {
c := NewContext(nil, nil)

v, err := c.Value[int64]("nope")
assert.ErrorIs(t, err, ErrNonExistentKey)
assert.Equal(t, int64(0), v)
}

func TestContextValueInvalidCast(t *testing.T) {
c := NewContext(nil, nil)

c.Set("key", int64(123))

v, err := c.Value[bool]("key")
assert.ErrorIs(t, err, ErrInvalidKeyType)
assert.False(t, v)
}

func TestContextValueStoredNilHasInvalidType(t *testing.T) {
c := NewContext(nil, nil)

c.Set("key", nil)

v, err := c.Value[any]("key")
assert.ErrorIs(t, err, ErrInvalidKeyType)
assert.Nil(t, v)
}

func TestContextValueOrOK(t *testing.T) {
c := NewContext(nil, nil)

c.Set("key", int64(123))

v, err := c.ValueOr[int64]("key", 999)
assert.NoError(t, err)
assert.Equal(t, int64(123), v)
}

func TestContextValueOrNonExistentKey(t *testing.T) {
c := NewContext(nil, nil)

v, err := c.ValueOr[int64]("nope", 999)
assert.NoError(t, err)
assert.Equal(t, int64(999), v)
}

func TestContextValueOrInvalidCast(t *testing.T) {
c := NewContext(nil, nil)

c.Set("key", int64(123))

v, err := c.ValueOr[float32]("key", float32(999))
assert.ErrorIs(t, err, ErrInvalidKeyType)
assert.Equal(t, float32(0), v)
}
Loading