abemedia / go-don

API framework written in Golang.
https://pkg.go.dev/github.com/abemedia/go-don
MIT License
53 stars 5 forks source link

Context request unmarshalling #5

Closed jarrettv closed 1 year ago

jarrettv commented 2 years ago

Idea to include context unmarshalling into the request struct or a simple way for custom model binding based on tags:

type WidgetPutReq struct {
    IsAuth         bool   `perm:"widget-manage"`
    UserName  string `context:"userName"`
    UrlCode      string `path:"code"`
    Widget
}
abemedia commented 2 years ago

Interesting idea. Maybe even add a mechanism for registering custom unmarshalers on a specific tag.

abemedia commented 2 years ago

I was thinking about this and providing the context values are basic types the decoder used for headers, path etc. could quite easily be re-used for context.

Making it work with arbitrary types including structs etc would be quite a bit more work though.

jarrettv commented 2 years ago

In hindsight, I understand that most context value keys aren't basic types and why this would be so difficult. However, I do like your idea of "registering custom unmarshalers on a specific tag".

abemedia commented 1 year ago

Hi @jarrettv ,

Sorry for leaving this issue sitting here for a while. Although it's slightly hacky it's actually possible to achieve this by using the decoder that's used for headers etc. in the handler function.

See the following example. Will close this as the option with registering decoders on tags is so niche that I'd rather not add it at this point.

package main

import (
    "context"
    "fmt"
    "log"

    "github.com/abemedia/go-don"
    "github.com/abemedia/go-don/decoder"
    _ "github.com/abemedia/go-don/encoding/json"
    "github.com/valyala/fasthttp"
)

type Context struct {
    context.Context
}

func (ctx Context) Get(key string) string {
    v := ctx.Context.Value(key)
    if v == nil {
        return ""
    }
    return fmt.Sprint(v)
}

func (ctx Context) Values(key string) []string {
    return []string{ctx.Get(key)}
}

var decodeContext = decoder.New("context")

type MyRequest struct {
    UserName string `context:"userName"`
}

func MyHandler(ctx context.Context, req MyRequest) (string, error) {
    err := decodeContext.Decode(Context{ctx}, &req)
    if err != nil {
        return "", err
    }
    return req.UserName, nil
}

func main() {
    r := don.New(&don.Config{DefaultEncoding: "application/json"})
    r.Post("/", don.H(MyHandler))

    // Mock middleware which adds the username.
    r.Use(func(next fasthttp.RequestHandler) fasthttp.RequestHandler {
        return func(ctx *fasthttp.RequestCtx) {
            ctx.SetUserValue("userName", "Test User")
            next(ctx)
        }
    })

    log.Fatal(r.ListenAndServe(":8080"))
}