From cfa3fcd64cc6304675f195f5020f3805aedde9d5 Mon Sep 17 00:00:00 2001 From: EwenQuim Date: Fri, 13 Dec 2024 22:02:44 +0100 Subject: [PATCH] Ctx initialisation --- ctx.go | 14 ++++++------ extra/fuegogin/adaptor.go | 47 ++++++++++++++++++++++++++++++--------- extra/fuegogin/context.go | 12 +++++++--- extra/fuegogin/lib/lib.go | 14 ++++++++---- mux.go | 14 ++++++------ serve.go | 6 ++--- serve_test.go | 10 ++++----- 7 files changed, 77 insertions(+), 40 deletions(-) diff --git a/ctx.go b/ctx.go index d50f862e..40b7860d 100644 --- a/ctx.go +++ b/ctx.go @@ -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. @@ -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. ) @@ -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) { diff --git a/extra/fuegogin/adaptor.go b/extra/fuegogin/adaptor.go index b2df568b..46215a6e 100644 --- a/extra/fuegogin/adaptor.go +++ b/extra/fuegogin/adaptor.go @@ -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, @@ -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 diff --git a/extra/fuegogin/context.go b/extra/fuegogin/context.go index 272a8fb4..b4ee1d1d 100644 --- a/extra/fuegogin/context.go +++ b/extra/fuegogin/context.go @@ -2,7 +2,8 @@ package fuegogin import ( "context" - "fmt" + "encoding/json" + "io" "net/http" "net/url" @@ -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 } diff --git a/extra/fuegogin/lib/lib.go b/extra/fuegogin/lib/lib.go index 78f2db25..969b24c4 100644 --- a/extra/fuegogin/lib/lib.go +++ b/extra/fuegogin/lib/lib.go @@ -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"), @@ -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 @@ -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 } diff --git a/mux.go b/mux.go index bc12cab1..e86ff736 100644 --- a/mux.go +++ b/mux.go @@ -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...) } @@ -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, diff --git a/serve.go b/serve.go index 4354ddfe..3475c205 100644 --- a/serve.go +++ b/serve.go @@ -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) @@ -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 { @@ -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{ diff --git a/serve_test.go b/serve_test.go index 8f11a599..2a856689 100644 --- a/serve_test.go +++ b/serve_test.go @@ -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, }) @@ -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, }) @@ -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, }) @@ -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, }) @@ -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, })