Fiber: 馃悰 session cannot store the structure

Created on 24 Nov 2020  路  4Comments  路  Source: gofiber/fiber

Fiber version

Issue description
After testing, the reason is that the session cannot store the structure
Code snippet

package main

import (
    "fmt"

    "github.com/gofiber/fiber/v2"
    "github.com/gofiber/fiber/v2/middleware/session"
)

// Auth Auth
type Auth struct {
    Name string
}

func main() {
    store := session.New()

    app := fiber.New()

    app.Get("/", func(c *fiber.Ctx) error {

        sess, err := store.Get(c)
        if err != nil {
            panic(err)
        }
        defer sess.Save()

        sess.Set("auth", Auth{
            Name: "ok",
        })

        return c.SendString("ok")
    })

    app.Get("/session", func(c *fiber.Ctx) error {
        sess, err := store.Get(c)
        if err != nil {
            panic(err)
        }
        defer sess.Save()

        auth := sess.Get("auth")

        fmt.Println("auth:", auth)

        return c.SendString("ok")
    })

    app.Listen("127.0.0.1:3008")
}

鈽笍 Bug 馃挕 Help wanted 馃攳 Under Investigation 馃毃 High Priority 馃洜 In Progress

Most helpful comment

The problem is in the following switch statement in write_bytes.go. Because of the user-defined struct, the switch statement chooses the default case, which leads to an ErrUnsupportedType error being thrown and the value not being stored, which leads to the (key, value) pair not being saved.

    switch v.Kind() {
    case reflect.Array, reflect.Slice:
        l := v.Len()
        b = AppendArrayHeader(b, uint32(l))
        for i := 0; i < l; i++ {
            b, err = AppendIntf(b, v.Index(i).Interface())
            if err != nil {
                return b, err
            }
        }
        return b, nil
    case reflect.Ptr:
        if v.IsNil() {
            return AppendNil(b), err
        }
        b, err = AppendIntf(b, v.Elem().Interface())
        return b, err
    default:
        return b, &ErrUnsupportedType{T: v.Type()}
    }

After some debugging, I found out that if I added the following case to the switch statement, it would follow that path. I unfortunately do not have any experience with msgp and I'm still a beginner in Go, so I don't really know how to proceed further.

case reflect.Struct:

All 4 comments

Just got the exact same error lol

But I have nothing to say, just an identical problem

The problem is in the session.Save() method. If you call session.Get() when you've set your value, but before session.Save() is called, you get the correct value back.

The problem is in the following switch statement in write_bytes.go. Because of the user-defined struct, the switch statement chooses the default case, which leads to an ErrUnsupportedType error being thrown and the value not being stored, which leads to the (key, value) pair not being saved.

    switch v.Kind() {
    case reflect.Array, reflect.Slice:
        l := v.Len()
        b = AppendArrayHeader(b, uint32(l))
        for i := 0; i < l; i++ {
            b, err = AppendIntf(b, v.Index(i).Interface())
            if err != nil {
                return b, err
            }
        }
        return b, nil
    case reflect.Ptr:
        if v.IsNil() {
            return AppendNil(b), err
        }
        b, err = AppendIntf(b, v.Elem().Interface())
        return b, err
    default:
        return b, &ErrUnsupportedType{T: v.Type()}
    }

After some debugging, I found out that if I added the following case to the switch statement, it would follow that path. I unfortunately do not have any experience with msgp and I'm still a beginner in Go, so I don't really know how to proceed further.

case reflect.Struct:

Okay, I kinda have it working now, but it's not perfect yet. This afternoon I did some research on msgp and as far as I know, it is not possible to convert the struct into bytes. Therefore, I'm converting the struct into a map[string]interface{}, which msgp can convert. My implementation uses github.com/fatih/structs to convert the structs into maps, after which I tell the program to append the map to the byte[].


case reflect.Struct:
    m := structs.Map(i)
    b, err = AppendMapStrIntf(b, m)
    return b, err

This results into the following output when running the snippet from @chinaapus:

auth: map[Name: ok]

There are two things I don't like about this implementation:

  1. We rely on an external, archived library
  2. We return a map[string]interface{} to the user and as far as I've seen, there is no way to convert a map[string]interface{} to a struct, without first defining the structure of the struct.

These things can be solved:

  1. We recreate the structs.Map() method in our code, as it is the only method out of the library we are using.
  2. We let the user convert the map[string]interface{} back into a struct. They'll have to define the structure of the final struct, then there are various methods/libraries to do the conversion itself.

@Fenny Let me know what you think about the implementation & disadvantages and I'll work on creating a PR.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

GrigoriyMikhalkin picture GrigoriyMikhalkin  路  4Comments

Aguezz picture Aguezz  路  4Comments

renanbastos93 picture renanbastos93  路  3Comments

Badrouu17 picture Badrouu17  路  4Comments

mewben picture mewben  路  3Comments