organise sessions examples

Former-commit-id: 682472d2cf4ebfc740687522fe5eef77b5bb1a72
This commit is contained in:
Gerasimos (Makis) Maropoulos 2020-05-07 07:34:17 +03:00
parent b4365cee8d
commit cd62ba3712
11 changed files with 190 additions and 411 deletions

View File

@ -139,8 +139,9 @@
* [Basic](cookies/basic/main.go)
* [Encode/Decode (with `securecookie`)](cookies/securecookie/main.go)
* Sessions
* [Overview](sessions/overview/main.go)
* [Middleware](sessions/middleware/main.go)
* [Overview: Config](sessions/overview/main.go)
* [Overview: Routes](sessions/overview/example/example.go)
* [Basic](sessions/basic/main.go)
* [Secure Cookie](sessions/securecookie/main.go)
* [Flash Messages](sessions/flash-messages/main.go)
* [Databases](sessions/database)

View File

@ -0,0 +1,51 @@
package main
import (
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/sessions"
)
const cookieNameForSessionID = "session_id_cookie"
func secret(ctx iris.Context) {
// Check if user is authenticated
if auth, _ := sessions.Get(ctx).GetBoolean("authenticated"); !auth {
ctx.StatusCode(iris.StatusForbidden)
return
}
// Print secret message
ctx.WriteString("The cake is a lie!")
}
func login(ctx iris.Context) {
session := sessions.Get(ctx)
// Authentication goes here
// ...
// Set user as authenticated
session.Set("authenticated", true)
}
func logout(ctx iris.Context) {
session := sessions.Get(ctx)
// Revoke users authentication
session.Set("authenticated", false)
}
func main() {
app := iris.New()
sess := sessions.New(sessions.Config{Cookie: cookieNameForSessionID, AllowReclaim: true})
app.Use(sess.Handler())
// ^ or comment this line and use sess.Start(ctx) inside your handlers
// instead of sessions.Get(ctx).
app.Get("/secret", secret)
app.Get("/login", login)
app.Get("/logout", logout)
app.Listen(":8080")
}

View File

@ -1,13 +1,14 @@
package main
import (
"errors"
"time"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/sessions"
"github.com/kataras/iris/v12/sessions/sessiondb/badger"
"github.com/kataras/iris/v12/_examples/sessions/overview/example"
)
func main() {
@ -34,75 +35,6 @@ func main() {
//
sess.UseDatabase(db)
// the rest of the code stays the same.
app := iris.New()
app.Get("/", func(ctx iris.Context) {
ctx.Writef("You should navigate to the /set, /get, /delete, /clear,/destroy instead")
})
app.Get("/set", func(ctx iris.Context) {
s := sess.Start(ctx)
// set session values
s.Set("name", "iris")
// test if set here
ctx.Writef("All ok session value of the 'name' is: %s", s.GetString("name"))
})
app.Get("/set/{key}/{value}", func(ctx iris.Context) {
key, value := ctx.Params().Get("key"), ctx.Params().Get("value")
s := sess.Start(ctx)
// set session values
s.Set(key, value)
// test if set here
ctx.Writef("All ok session value of the '%s' is: %s", key, s.GetString(key))
})
app.Get("/get", func(ctx iris.Context) {
// get a specific key, as string, if no found returns just an empty string
name := sess.Start(ctx).GetString("name")
ctx.Writef("The 'name' on the /set was: %s", name)
})
app.Get("/get/{key}", func(ctx iris.Context) {
// get a specific key, as string, if no found returns just an empty string
name := sess.Start(ctx).GetString(ctx.Params().Get("key"))
ctx.Writef("The name on the /set was: %s", name)
})
app.Get("/delete", func(ctx iris.Context) {
// delete a specific key
sess.Start(ctx).Delete("name")
})
app.Get("/clear", func(ctx iris.Context) {
// removes all entries
sess.Start(ctx).Clear()
})
app.Get("/destroy", func(ctx iris.Context) {
// destroy, removes the entire session data and cookie
sess.Destroy(ctx)
})
app.Get("/update", func(ctx iris.Context) {
// updates resets the expiration based on the session's `Expires` field.
if err := sess.ShiftExpiration(ctx); err != nil {
if errors.Is(err, sessions.ErrNotFound) {
ctx.StatusCode(iris.StatusNotFound)
} else if errors.Is(err, sessions.ErrNotImplemented) {
ctx.StatusCode(iris.StatusNotImplemented)
} else {
ctx.StatusCode(iris.StatusNotModified)
}
ctx.Writef("%v", err)
ctx.Application().Logger().Error(err)
}
})
app := example.NewApp(sess)
app.Listen(":8080")
}

View File

@ -1,7 +1,6 @@
package main
import (
"errors"
"os"
"time"
@ -9,6 +8,8 @@ import (
"github.com/kataras/iris/v12/sessions"
"github.com/kataras/iris/v12/sessions/sessiondb/boltdb"
"github.com/kataras/iris/v12/_examples/sessions/overview/example"
)
func main() {
@ -23,13 +24,17 @@ func main() {
})
defer db.Close() // close and unlock the database if application errored.
sess := sessions.New(sessions.Config{
Cookie: "sessionscookieid",
Expires: 45 * time.Minute, // <=0 means unlimited life. Defaults to 0.
AllowReclaim: true,
})
//
// IMPORTANT:
//
sess.UseDatabase(db)
// The default database's values encoder and decoder
// calls the value's `Marshal/Unmarshal` methods (if any)
// otherwise JSON is selected,
@ -50,83 +55,9 @@ func main() {
// i.e: a transcoder which will allow(on Marshal: return its byte representation and nil error)
// or dissalow(on Marshal: return non nil error) certain types.
//
// gob.Register(example.BusinessModel{})
// sessions.DefaultTranscoder = sessions.GobTranscoder{}
//
// IMPORTANT:
//
sess.UseDatabase(db)
// the rest of the code stays the same.
app := iris.New()
app.Logger().SetLevel("debug")
app.Get("/", func(ctx iris.Context) {
ctx.Writef("You should navigate to the /set, /get, /delete, /clear,/destroy instead")
})
app.Get("/set", func(ctx iris.Context) {
s := sess.Start(ctx)
// set session values
s.Set("name", "iris")
// test if set here
ctx.Writef("All ok session value of the 'name' is: %s", s.GetString("name"))
})
app.Get("/set/{key}/{value}", func(ctx iris.Context) {
key, value := ctx.Params().Get("key"), ctx.Params().Get("value")
s := sess.Start(ctx)
// set session values
s.Set(key, value)
// test if set here
ctx.Writef("All ok session value of the '%s' is: %s", key, s.GetString(key))
})
app.Get("/get", func(ctx iris.Context) {
// get a specific key, as string, if no found returns just an empty string
name := sess.Start(ctx).GetString("name")
ctx.Writef("The 'name' on the /set was: %s", name)
})
app.Get("/get/{key}", func(ctx iris.Context) {
// get a specific key, as string, if no found returns just an empty string
name := sess.Start(ctx).GetString(ctx.Params().Get("key"))
ctx.Writef("The name on the /set was: %s", name)
})
app.Get("/delete", func(ctx iris.Context) {
// delete a specific key
sess.Start(ctx).Delete("name")
})
app.Get("/clear", func(ctx iris.Context) {
// removes all entries
sess.Start(ctx).Clear()
})
app.Get("/destroy", func(ctx iris.Context) {
// destroy, removes the entire session data and cookie
sess.Destroy(ctx)
})
app.Get("/update", func(ctx iris.Context) {
// updates resets the expiration based on the session's `Expires` field.
if err := sess.ShiftExpiration(ctx); err != nil {
if errors.Is(err, sessions.ErrNotFound) {
ctx.StatusCode(iris.StatusNotFound)
} else if errors.Is(err, sessions.ErrNotImplemented) {
ctx.StatusCode(iris.StatusNotImplemented)
} else {
ctx.StatusCode(iris.StatusNotModified)
}
ctx.Writef("%v", err)
ctx.Application().Logger().Error(err)
}
})
app := example.NewApp(sess)
app.Listen(":8080")
}

View File

@ -1,13 +1,14 @@
package main
import (
"errors"
"time"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/sessions"
"github.com/kataras/iris/v12/sessions/sessiondb/redis"
"github.com/kataras/iris/v12/_examples/sessions/overview/example"
)
// tested with redis version 3.0.503.
@ -42,7 +43,7 @@ func main() {
defer db.Close() // close the database connection if application errored.
sess := sessions.New(sessions.Config{
Cookie: "sessionscookieid",
Cookie: "_session_id",
Expires: 0, // defaults to 0: unlimited life. Another good value is: 45 * time.Minute,
AllowReclaim: true,
CookieSecureTLS: true,
@ -53,97 +54,6 @@ func main() {
//
sess.UseDatabase(db)
// the rest of the code stays the same.
app := iris.New()
app.Get("/", func(ctx iris.Context) {
ctx.Writef("You should navigate to the /set, /get, /delete, /clear,/destroy instead")
})
app.Get("/set", func(ctx iris.Context) {
session := sessions.Get(ctx)
// set session values
session.Set("name", "iris")
// test if set here
ctx.Writef("All ok session value of the 'name' is: %s", session.GetString("name"))
})
app.Get("/set/{key}/{value}", func(ctx iris.Context) {
key, value := ctx.Params().Get("key"), ctx.Params().Get("value")
session := sessions.Get(ctx)
// set session values
session.Set(key, value)
// test if set here
ctx.Writef("All ok session value of the '%s' is: %s", key, session.GetString(key))
})
app.Get("/set/int/{key}/{value}", func(ctx iris.Context) {
key := ctx.Params().Get("key")
value, _ := ctx.Params().GetInt("value")
session := sessions.Get(ctx)
// set session values
session.Set(key, value)
valueSet := session.Get(key)
// test if set here
ctx.Writef("All ok session value of the '%s' is: %v", key, valueSet)
})
app.Get("/get/{key}", func(ctx iris.Context) {
key := ctx.Params().Get("key")
session := sessions.Get(ctx)
value := session.Get(key)
ctx.Writef("The '%s' on the /set was: %v", key, value)
})
app.Get("/get", func(ctx iris.Context) {
// get a specific key, as string, if no found returns just an empty string
session := sessions.Get(ctx)
name := session.GetString("name")
ctx.Writef("The 'name' on the /set was: %s", name)
})
app.Get("/get/{key}", func(ctx iris.Context) {
// get a specific key, as string, if no found returns just an empty string
session := sessions.Get(ctx)
name := session.GetString(ctx.Params().Get("key"))
ctx.Writef("The name on the /set was: %s", name)
})
app.Get("/delete", func(ctx iris.Context) {
// delete a specific key
sessions.Get(ctx).Delete("name")
})
app.Get("/clear", func(ctx iris.Context) {
// removes all entries
sessions.Get(ctx).Clear()
})
app.Get("/destroy", func(ctx iris.Context) {
// destroy, removes the entire session data and cookie
sess.Destroy(ctx)
})
app.Get("/update", func(ctx iris.Context) {
// updates resets the expiration based on the session's `Expires` field.
if err := sess.ShiftExpiration(ctx); err != nil {
if errors.Is(err, sessions.ErrNotFound) {
ctx.StatusCode(iris.StatusNotFound)
} else if errors.Is(err, sessions.ErrNotImplemented) {
ctx.StatusCode(iris.StatusNotImplemented)
} else {
ctx.StatusCode(iris.StatusNotModified)
}
ctx.Writef("%v", err)
ctx.Application().Logger().Error(err)
}
})
app := example.NewApp(sess)
app.Listen(":8080")
}

View File

@ -8,16 +8,18 @@ import (
func main() {
app := iris.New()
sess := sessions.New(sessions.Config{Cookie: "myappsessionid", AllowReclaim: true})
sess := sessions.New(sessions.Config{Cookie: "_session_id", AllowReclaim: true})
app.Use(sess.Handler())
app.Get("/set", func(ctx iris.Context) {
s := sess.Start(ctx)
s := sessions.Get(ctx)
s.SetFlash("name", "iris")
ctx.Writef("Message set, is available for the next request")
})
app.Get("/get", func(ctx iris.Context) {
s := sess.Start(ctx)
s := sessions.Get(ctx)
name := s.GetFlashString("name")
if name == "" {
ctx.Writef("Empty name!!")
@ -27,7 +29,7 @@ func main() {
})
app.Get("/test", func(ctx iris.Context) {
s := sess.Start(ctx)
s := sessions.Get(ctx)
name := s.GetFlashString("name")
if name == "" {
ctx.Writef("Empty name!!")

View File

@ -1,34 +1,21 @@
package main
package example
import (
"time"
"errors"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/sessions"
)
type businessModel struct {
// BusinessModel is just a Go struct value that we will use in our session example,
// never save sensitive information, like passwords, here.
type BusinessModel struct {
Name string
}
func main() {
// NewApp returns a new application for showcasing the sessions feature.
func NewApp(sess *sessions.Sessions) *iris.Application {
app := iris.New()
sess := sessions.New(sessions.Config{
// Cookie string, the session's client cookie name, for example: "mysessionid"
//
// Defaults to "irissessionid"
Cookie: "mysessionid",
// it's time.Duration, from the time cookie is created, how long it can be alive?
// 0 means no expire.
// -1 means expire when browser closes
// or set a value, like 2 hours:
Expires: time.Hour * 2,
// if you want to invalid cookies on different subdomains
// of the same host, then enable it.
// Defaults to false.
DisableSubdomainPersistence: false,
})
app.Use(sess.Handler()) // session is always non-nil inside handlers now.
app.Get("/", func(ctx iris.Context) {
@ -51,57 +38,85 @@ func main() {
session := sessions.Get(ctx)
session.Set("name", "iris")
// test if set here.
ctx.Writef("All ok session set to: %s", session.GetString("name"))
// Set will set the value as-it-is,
// if it's a slice or map
// you will be able to change it on .Get directly!
// Keep note that I don't recommend saving big data neither slices or maps on a session
// but if you really need it then use the `SetImmutable` instead of `Set`.
// Use `SetImmutable` consistently, it's slower.
// Read more about muttable and immutable go types: https://stackoverflow.com/a/8021081
})
app.Get("/set/{key}/{value}", func(ctx iris.Context) {
key, value := ctx.Params().Get("key"), ctx.Params().Get("value")
session := sessions.Get(ctx)
session.Set(key, value)
// test if set here
ctx.Writef("All ok session value of the '%s' is: %s", key, session.GetString(key))
})
app.Get("/get", func(ctx iris.Context) {
session := sessions.Get(ctx)
// get a specific value, as string,
// if not found then it returns just an empty string.
name := sessions.Get(ctx).GetString("name")
name := session.GetString("name")
ctx.Writef("The name on the /set was: %s", name)
})
app.Get("/set-struct", func(ctx iris.Context) {
session := sessions.Get(ctx)
session.Set("struct", BusinessModel{Name: "John Doe"})
ctx.Writef("All ok session value of the 'struct' is: %v", session.Get("struct"))
})
app.Get("/get-struct", func(ctx iris.Context) {
session := sessions.Get(ctx)
ctx.Writef("Session value of the 'struct' is: %v", session.Get("struct"))
})
app.Get("/set/{key}/{value}", func(ctx iris.Context) {
session := sessions.Get(ctx)
key := ctx.Params().Get("key")
value := ctx.Params().Get("value")
session.Set(key, value)
ctx.Writef("All ok session value of the '%s' is: %s", key, session.GetString(key))
})
app.Get("/get/{key}", func(ctx iris.Context) {
session := sessions.Get(ctx)
// get a specific key, as string, if no found returns just an empty string
key := ctx.Params().Get("key")
name := session.GetString(key)
ctx.Writef("The name on the /set was: %s", name)
})
app.Get("/delete", func(ctx iris.Context) {
session := sessions.Get(ctx)
// delete a specific key
sessions.Get(ctx).Delete("name")
session.Delete("name")
})
app.Get("/clear", func(ctx iris.Context) {
session := sessions.Get(ctx)
// removes all entries.
sessions.Get(ctx).Clear()
session.Clear()
})
app.Get("/update", func(ctx iris.Context) {
// updates expire date.
sess.ShiftExpiration(ctx)
session := sessions.Get(ctx)
// shifts the expiration based on the session's `Lifetime`.
if err := session.Man.ShiftExpiration(ctx); err != nil {
if errors.Is(err, sessions.ErrNotFound) {
ctx.StatusCode(iris.StatusNotFound)
} else if errors.Is(err, sessions.ErrNotImplemented) {
ctx.StatusCode(iris.StatusNotImplemented)
} else {
ctx.StatusCode(iris.StatusNotModified)
}
ctx.Writef("%v", err)
ctx.Application().Logger().Error(err)
}
})
app.Get("/destroy", func(ctx iris.Context) {
// destroy, removes the entire session data and cookie
// sess.Destroy(ctx)
// or
sessions.Get(ctx).Destroy()
session := sessions.Get(ctx)
// Man(anager)'s Destroy, removes the entire session data and cookie
session.Man.Destroy(ctx)
})
// Note about Destroy:
//
// You can destroy a session outside of a handler too, using the:
@ -114,25 +129,26 @@ func main() {
//
// Use `SetImmutable` consistently, it's slower than `Set`.
// Read more about muttable and immutable go types: https://stackoverflow.com/a/8021081
app.Get("/set_immutable", func(ctx iris.Context) {
business := []businessModel{{Name: "Edward"}, {Name: "value 2"}}
app.Get("/set-immutable", func(ctx iris.Context) {
session := sessions.Get(ctx)
business := []BusinessModel{{Name: "Edward"}, {Name: "value 2"}}
session.SetImmutable("businessEdit", business)
businessGet := session.Get("businessEdit").([]businessModel)
businessGet := session.Get("businessEdit").([]BusinessModel)
// try to change it, if we used `Set` instead of `SetImmutable` this
// change will affect the underline array of the session's value "businessEdit", but now it will not.
businessGet[0].Name = "Gabriel"
})
app.Get("/get_immutable", func(ctx iris.Context) {
app.Get("/get-immutable", func(ctx iris.Context) {
valSlice := sessions.Get(ctx).Get("businessEdit")
if valSlice == nil {
ctx.HTML("please navigate to the <a href='/set_immutable'>/set_immutable</a> first")
ctx.HTML("please navigate to the <a href='/set_immutable'>/set-immutable</a> first")
return
}
firstModel := valSlice.([]businessModel)[0]
firstModel := valSlice.([]BusinessModel)[0]
// businessGet[0].Name is equal to Edward initially
if firstModel.Name != "Edward" {
panic("Report this as a bug, immutable data cannot be changed from the caller without re-SetImmutable")
@ -143,5 +159,5 @@ func main() {
// the name should remains "Edward"
})
app.Listen(":8080")
return app
}

View File

@ -1,50 +1,31 @@
package main
import (
"github.com/kataras/iris/v12"
"time"
"github.com/kataras/iris/v12/_examples/sessions/overview/example"
"github.com/kataras/iris/v12/sessions"
)
var (
cookieNameForSessionID = "mycookiesessionnameid"
sess = sessions.New(sessions.Config{Cookie: cookieNameForSessionID, AllowReclaim: true})
)
func secret(ctx iris.Context) {
// Check if user is authenticated
if auth, _ := sess.Start(ctx).GetBoolean("authenticated"); !auth {
ctx.StatusCode(iris.StatusForbidden)
return
}
// Print secret message
ctx.WriteString("The cake is a lie!")
}
func login(ctx iris.Context) {
session := sess.Start(ctx)
// Authentication goes here
// ...
// Set user as authenticated
session.Set("authenticated", true)
}
func logout(ctx iris.Context) {
session := sess.Start(ctx)
// Revoke users authentication
session.Set("authenticated", false)
}
func main() {
app := iris.New()
app.Get("/secret", secret)
app.Get("/login", login)
app.Get("/logout", logout)
sess := sessions.New(sessions.Config{
// Cookie string, the session's client cookie name, for example: "_session_id"
//
// Defaults to "irissessionid"
Cookie: "_session_id",
// it's time.Duration, from the time cookie is created, how long it can be alive?
// 0 means no expire, unlimited life.
// -1 means expire when browser closes
// or set a value, like 2 hours:
Expires: time.Hour * 2,
// if you want to invalid cookies on different subdomains
// of the same host, then enable it.
// Defaults to false.
DisableSubdomainPersistence: false,
// Allow getting the session value stored by the request from the same request.
AllowReclaim: true,
})
app := example.NewApp(sess)
app.Listen(":8080")
}

View File

@ -7,16 +7,17 @@ package main
import (
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/sessions"
"github.com/kataras/iris/v12/_examples/sessions/overview/example"
"github.com/gorilla/securecookie"
)
func newApp() *iris.Application {
app := iris.New()
cookieName := "mycustomsessionid"
cookieName := "_session_id"
// AES only supports key sizes of 16, 24 or 32 bytes.
// You either need to provide exactly that amount or you derive the key from what you type in.
hashKey := []byte("the-big-and-secret-fash-key-here")
@ -30,52 +31,7 @@ func newApp() *iris.Application {
AllowReclaim: true,
})
app.Get("/", func(ctx iris.Context) {
ctx.Writef("You should navigate to the /set, /get, /delete, /clear,/destroy instead")
})
app.Get("/set", func(ctx iris.Context) {
// set session values
s := mySessions.Start(ctx)
s.Set("name", "iris")
// test if set here
ctx.Writef("All ok session set to: %s", s.GetString("name"))
})
app.Get("/get", func(ctx iris.Context) {
// get a specific key, as string, if no found returns just an empty string
s := mySessions.Start(ctx)
name := s.GetString("name")
ctx.Writef("The name on the /set was: %s", name)
})
app.Get("/delete", func(ctx iris.Context) {
// delete a specific key
s := mySessions.Start(ctx)
s.Delete("name")
})
app.Get("/clear", func(ctx iris.Context) {
// removes all entries
mySessions.Start(ctx).Clear()
})
app.Get("/update", func(ctx iris.Context) {
// updates expire date with a new date
mySessions.ShiftExpiration(ctx)
})
app.Get("/destroy", func(ctx iris.Context) {
// destroy, removes the entire session data and cookie
mySessions.Destroy(ctx)
})
// Note about destroy:
//
// You can destroy a session outside of a handler too, using the:
// mySessions.DestroyByID
// mySessions.DestroyAll
app = example.NewApp(mySessions)
return app
}

View File

@ -30,6 +30,10 @@ func newProvider() *provider {
// RegisterDatabase sets a session database.
func (p *provider) RegisterDatabase(db Database) {
if db == nil {
return
}
p.mu.Lock() // for any case
p.db = db
p.mu.Unlock()

View File

@ -5,8 +5,13 @@ import (
"encoding/gob"
"encoding/json"
"reflect"
"time"
)
func init() {
gob.Register(time.Time{})
}
type (
// Marshaler is the common marshaler interface, used by transcoder.
Marshaler interface {
@ -23,6 +28,12 @@ type (
}
)
type (
defaultTranscoder struct{}
// GobTranscoder can be set to `DefaultTranscoder` to modify the database(s) transcoder.
GobTranscoder struct{}
)
var (
_ Transcoder = (*defaultTranscoder)(nil)
_ Transcoder = (*GobTranscoder)(nil)
@ -53,12 +64,6 @@ var (
DefaultTranscoder Transcoder = defaultTranscoder{}
)
type (
defaultTranscoder struct{}
// GobTranscoder can be set to `DefaultTranscoder` to modify the database(s) transcoder.
GobTranscoder struct{}
)
func (defaultTranscoder) Marshal(value interface{}) ([]byte, error) {
if tr, ok := value.(Marshaler); ok {
return tr.Marshal(value)
@ -91,17 +96,10 @@ func (GobTranscoder) Marshal(value interface{}) ([]byte, error) {
err error
)
switch v := value.(type) {
case reflect.Value:
if v, ok := value.(reflect.Value); ok {
err = enc.EncodeValue(v)
case string,
int, int8, int16, int32, int64,
uint, uint8, uint16, uint32, uint64,
float32, float64,
complex64, complex128:
err = enc.Encode(&v)
default:
err = enc.Encode(value)
} else {
err = enc.Encode(&value)
}
if err != nil {
@ -114,10 +112,7 @@ func (GobTranscoder) Marshal(value interface{}) ([]byte, error) {
// Unmarshal parses the gob-encoded data "b" and stores the result
// in the value pointed to by "outPtr".
func (GobTranscoder) Unmarshal(b []byte, outPtr interface{}) error {
var (
r = bytes.NewBuffer(b)
dec = gob.NewDecoder(r)
)
dec := gob.NewDecoder(bytes.NewBuffer(b))
if v, ok := outPtr.(reflect.Value); ok {
return dec.DecodeValue(v)