Skip to content

Commit

Permalink
Ctx initialisation
Browse files Browse the repository at this point in the history
  • Loading branch information
EwenQuim committed Dec 14, 2024
1 parent 291e094 commit 445a150
Show file tree
Hide file tree
Showing 7 changed files with 77 additions and 40 deletions.
14 changes: 7 additions & 7 deletions ctx.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ const (
maxBodySize = 1048576
)

// ctx is the context of the request.
// Ctx is the context of the request.
// It contains the request body, the path parameters, the query parameters, and the HTTP request.
// Please do not use a pointer type as parameter.
type ctx[B any] interface {
type Ctx[B any] interface {
// Body returns the body of the request.
// If (*B) implements [InTransformer], it will be transformed after deserialization.
// It caches the result, so it can be called multiple times.
Expand Down Expand Up @@ -129,7 +129,7 @@ type ContextNoBody struct {
}

var (
_ ctx[any] = ContextNoBody{} // Check that ContextNoBody implements Ctx.
_ Ctx[any] = ContextNoBody{} // Check that ContextNoBody implements Ctx.
_ context.Context = ContextNoBody{} // Check that ContextNoBody implements context.Context.
)

Expand Down Expand Up @@ -160,10 +160,10 @@ type readOptions struct {
}

var (
_ ctx[any] = &ContextWithBody[any]{} // Check that ContextWithBody[any] implements Ctx.
_ ctx[string] = &ContextWithBody[string]{} // Check that ContextWithBody[string] implements Ctx.
_ ctx[any] = &ContextNoBody{} // Check that ContextNoBody implements Ctx.
_ ctx[any] = ContextNoBody{} // Check that ContextNoBody implements Ctx.
_ Ctx[any] = &ContextWithBody[any]{} // Check that ContextWithBody[any] implements Ctx.
_ Ctx[string] = &ContextWithBody[string]{} // Check that ContextWithBody[string] implements Ctx.
_ Ctx[any] = &ContextNoBody{} // Check that ContextNoBody implements Ctx.
_ Ctx[any] = ContextNoBody{} // Check that ContextNoBody implements Ctx.
)

func (c ContextNoBody) Redirect(code int, url string) (any, error) {
Expand Down
47 changes: 36 additions & 11 deletions extra/fuegogin/adaptor.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,35 +7,55 @@ import (
"github.com/go-fuego/fuego"
)

func Get[T, B any](
func Get[T, B any, Contexted fuego.Ctx[B]](
s *fuego.OpenAPI,
e *gin.Engine,
path string,
handler func(c *ContextWithBody[T]) (B, error),
handler func(c Contexted) (T, error),
options ...func(*fuego.BaseRoute),
) *fuego.Route[B, T] {
) *fuego.Route[T, B] {
return Handle(s, e, "GET", path, handler, options...)
}

func Post[T, B any](
func Post[T, B any, Contexted fuego.Ctx[B]](
s *fuego.OpenAPI,
e *gin.Engine,
path string,
handler func(c *ContextWithBody[T]) (B, error),
handler func(c Contexted) (T, error),
options ...func(*fuego.BaseRoute),
) *fuego.Route[B, T] {
) *fuego.Route[T, B] {
return Handle(s, e, "POST", path, handler, options...)
}

func Handle[T, B any](
// initializes any Context type with the base ContextNoBody context.
//
// var ctx ContextWithBody[any] // does not work because it will create a ContextWithBody[any] with a nil value
func InitContext[Contextable fuego.Ctx[Body], Body any](baseContext ContextNoBody) (Contextable, error) {
var c Contextable

switch any(c).(type) {
case ContextNoBody:
return any(baseContext).(Contextable), nil
case *ContextNoBody:
return any(&baseContext).(Contextable), nil
case *ContextWithBody[Body]:
return any(&ContextWithBody[Body]{
ContextNoBody: baseContext,
}).(Contextable), nil
default:
panic("unknown type")
}
}

func Handle[T, B any, Contexted fuego.Ctx[B]](
openapi *fuego.OpenAPI,
e *gin.Engine,
method,
path string,
handler func(c *ContextWithBody[T]) (B, error),
handler func(c Contexted) (T, error),
options ...func(*fuego.BaseRoute),
) *fuego.Route[B, T] {
route := &fuego.Route[B, T]{
) *fuego.Route[T, B] {
route := &fuego.Route[T, B]{
BaseRoute: fuego.BaseRoute{
Method: method,
Path: path,
Expand All @@ -56,7 +76,12 @@ func Handle[T, B any](
ginCtx: c,
},
}
ans, err := handler(context)
ccc, err := InitContext[Contexted](context.ContextNoBody)
if err != nil {
c.Error(err)
return
}
ans, err := handler(ccc)
if err != nil {
c.Error(err)
return
Expand Down
12 changes: 9 additions & 3 deletions extra/fuegogin/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ package fuegogin

import (
"context"
"fmt"
"encoding/json"
"io"
"net/http"
"net/url"

Expand All @@ -22,9 +23,14 @@ type ContextNoBody struct {

// Body implements fuego.Ctx.
func (c *ContextWithBody[B]) Body() (B, error) {
fmt.Println("c.ginCtx", c.ginCtx)
bytes, err := io.ReadAll(c.ginCtx.Request.Body)
if err != nil {
return *new(B), err
}

var body B
err := c.ginCtx.Bind(&body)
err = json.Unmarshal(bytes, &body)

return body, err
}

Expand Down
14 changes: 10 additions & 4 deletions extra/fuegogin/lib/lib.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ func SetupGin() (*gin.Engine, *fuego.OpenAPI) {
e.GET("/gin", ginController)

// Register to Gin router with Fuego wrapper for same OpenAPI spec
fuegogin.Get(openapi, e, "/fuego", fuegoController)
fuegogin.Get(openapi, e, "/fuego-with-options", fuegoController,
fuegogin.Get(openapi, e, "/fuego", fuegoControllerGet)
fuegogin.Post(openapi, e, "/fuego-with-options", fuegoControllerPost,
option.Description("Some description"),
option.OperationID("SomeOperationID"),
option.AddError(409, "Name Already Exists"),
Expand All @@ -50,7 +50,13 @@ func ginController(c *gin.Context) {
c.String(200, "pong")
}

func fuegoController(c *fuegogin.ContextWithBody[HelloRequest]) (HelloResponse, error) {
func fuegoControllerGet(c *fuegogin.ContextNoBody) (HelloResponse, error) {
return HelloResponse{
Message: "Hello",
}, nil
}

func fuegoControllerPost(c *fuegogin.ContextWithBody[HelloRequest]) (HelloResponse, error) {
body, err := c.Body()
if err != nil {
return HelloResponse{}, err
Expand All @@ -61,7 +67,7 @@ func fuegoController(c *fuegogin.ContextWithBody[HelloRequest]) (HelloResponse,
fmt.Println("name", name)

return HelloResponse{
Message: "Hello " + body.Name,
Message: "Hello " + body.Name + name,
}, nil
}

Expand Down
14 changes: 7 additions & 7 deletions mux.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,27 +71,27 @@ func (r *BaseRoute) GenerateDefaultDescription(otherMiddlewares ...func(http.Han
}

// Capture all methods (GET, POST, PUT, PATCH, DELETE) and register a controller.
func All[ReturnType, Body any, Contexted ctx[Body]](s *Server, path string, controller func(Contexted) (ReturnType, error), options ...func(*BaseRoute)) *Route[ReturnType, Body] {
func All[ReturnType, Body any, Contexted Ctx[Body]](s *Server, path string, controller func(Contexted) (ReturnType, error), options ...func(*BaseRoute)) *Route[ReturnType, Body] {
return registerFuegoController(s, "", path, controller, options...)
}

func Get[T, B any, Contexted ctx[B]](s *Server, path string, controller func(Contexted) (T, error), options ...func(*BaseRoute)) *Route[T, B] {
func Get[T, B any, Contexted Ctx[B]](s *Server, path string, controller func(Contexted) (T, error), options ...func(*BaseRoute)) *Route[T, B] {
return registerFuegoController(s, http.MethodGet, path, controller, options...)
}

func Post[T, B any, Contexted ctx[B]](s *Server, path string, controller func(Contexted) (T, error), options ...func(*BaseRoute)) *Route[T, B] {
func Post[T, B any, Contexted Ctx[B]](s *Server, path string, controller func(Contexted) (T, error), options ...func(*BaseRoute)) *Route[T, B] {
return registerFuegoController(s, http.MethodPost, path, controller, options...)
}

func Delete[T, B any, Contexted ctx[B]](s *Server, path string, controller func(Contexted) (T, error), options ...func(*BaseRoute)) *Route[T, B] {
func Delete[T, B any, Contexted Ctx[B]](s *Server, path string, controller func(Contexted) (T, error), options ...func(*BaseRoute)) *Route[T, B] {
return registerFuegoController(s, http.MethodDelete, path, controller, options...)
}

func Put[T, B any, Contexted ctx[B]](s *Server, path string, controller func(Contexted) (T, error), options ...func(*BaseRoute)) *Route[T, B] {
func Put[T, B any, Contexted Ctx[B]](s *Server, path string, controller func(Contexted) (T, error), options ...func(*BaseRoute)) *Route[T, B] {
return registerFuegoController(s, http.MethodPut, path, controller, options...)
}

func Patch[T, B any, Contexted ctx[B]](s *Server, path string, controller func(Contexted) (T, error), options ...func(*BaseRoute)) *Route[T, B] {
func Patch[T, B any, Contexted Ctx[B]](s *Server, path string, controller func(Contexted) (T, error), options ...func(*BaseRoute)) *Route[T, B] {
return registerFuegoController(s, http.MethodPatch, path, controller, options...)
}

Expand Down Expand Up @@ -167,7 +167,7 @@ func PatchStd(s *Server, path string, controller func(http.ResponseWriter, *http
return registerStdController(s, http.MethodPatch, path, controller, options...)
}

func registerFuegoController[T, B any, Contexted ctx[B]](s *Server, method, path string, controller func(Contexted) (T, error), options ...func(*BaseRoute)) *Route[T, B] {
func registerFuegoController[T, B any, Contexted Ctx[B]](s *Server, method, path string, controller func(Contexted) (T, error), options ...func(*BaseRoute)) *Route[T, B] {
route := BaseRoute{
Method: method,
Path: path,
Expand Down
6 changes: 3 additions & 3 deletions serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ func (s *Server) url() string {
// initializes any Context type with the base ContextNoBody context.
//
// var ctx ContextWithBody[any] // does not work because it will create a ContextWithBody[any] with a nil value
func initContext[Contextable ctx[Body], Body any](baseContext ContextNoBody) (Contextable, error) {
func InitContext[Contextable Ctx[Body], Body any](baseContext ContextNoBody) (Contextable, error) {
var c Contextable

err := validateParams(baseContext)
Expand All @@ -86,7 +86,7 @@ func initContext[Contextable ctx[Body], Body any](baseContext ContextNoBody) (Co
// HTTPHandler converts a Fuego controller into a http.HandlerFunc.
// Uses Server for configuration.
// Uses Route for route configuration. Optional.
func HTTPHandler[ReturnType, Body any, Contextable ctx[Body]](s *Server, controller func(c Contextable) (ReturnType, error), route *BaseRoute) http.HandlerFunc {
func HTTPHandler[ReturnType, Body any, Contextable Ctx[Body]](s *Server, controller func(c Contextable) (ReturnType, error), route *BaseRoute) http.HandlerFunc {
// Just a check, not used at request time
baseContext := *new(Contextable)
if reflect.TypeOf(baseContext) == nil {
Expand All @@ -109,7 +109,7 @@ func HTTPHandler[ReturnType, Body any, Contextable ctx[Body]](s *Server, control
templates = template.Must(s.template.Clone())
}

ctx, err := initContext[Contextable](ContextNoBody{
ctx, err := InitContext[Contextable](ContextNoBody{
Req: r,
Res: w,
readOptions: readOptions{
Expand Down
10 changes: 5 additions & 5 deletions serve_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,7 @@ func TestIni(t *testing.T) {
t.Run("can initialize ContextNoBody", func(t *testing.T) {
req := httptest.NewRequest("GET", "/ctx/error-in-rendering", nil)
w := httptest.NewRecorder()
ctx, err := initContext[ContextNoBody](ContextNoBody{
ctx, err := InitContext[ContextNoBody](ContextNoBody{
Req: req,
Res: w,
})
Expand All @@ -365,7 +365,7 @@ func TestIni(t *testing.T) {
t.Run("can initialize ContextNoBody", func(t *testing.T) {
req := httptest.NewRequest("GET", "/ctx/error-in-rendering", nil)
w := httptest.NewRecorder()
ctx, err := initContext[*ContextNoBody](ContextNoBody{
ctx, err := InitContext[*ContextNoBody](ContextNoBody{
Req: req,
Res: w,
})
Expand All @@ -379,7 +379,7 @@ func TestIni(t *testing.T) {
t.Run("can initialize ContextWithBody[string]", func(t *testing.T) {
req := httptest.NewRequest("GET", "/ctx/error-in-rendering", nil)
w := httptest.NewRecorder()
ctx, err := initContext[*ContextWithBody[string]](ContextNoBody{
ctx, err := InitContext[*ContextWithBody[string]](ContextNoBody{
Req: req,
Res: w,
})
Expand All @@ -393,7 +393,7 @@ func TestIni(t *testing.T) {
t.Run("can initialize ContextWithBody[struct]", func(t *testing.T) {
req := httptest.NewRequest("GET", "/ctx/error-in-rendering", nil)
w := httptest.NewRecorder()
ctx, err := initContext[*ContextWithBody[ans]](ContextNoBody{
ctx, err := InitContext[*ContextWithBody[ans]](ContextNoBody{
Req: req,
Res: w,
})
Expand All @@ -409,7 +409,7 @@ func TestIni(t *testing.T) {
w := httptest.NewRecorder()

require.Panics(t, func() {
initContext[ctx[any]](ContextNoBody{
InitContext[Ctx[any]](ContextNoBody{
Req: req,
Res: w,
})
Expand Down

0 comments on commit 445a150

Please sign in to comment.