Skip to content

Commit

Permalink
Tried using interface for context in fuegogin
Browse files Browse the repository at this point in the history
  • Loading branch information
EwenQuim committed Dec 17, 2024
1 parent cc93ae2 commit a4861bb
Show file tree
Hide file tree
Showing 9 changed files with 163 additions and 79 deletions.
14 changes: 9 additions & 5 deletions ctx.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,15 @@ const (
// 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 {
CommonCtx[B]

Context() context.Context

Request() *http.Request // Request returns the underlying HTTP request.
Response() http.ResponseWriter // Response returns the underlying HTTP response writer.
}

type CommonCtx[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 @@ -70,11 +79,6 @@ type Ctx[B any] interface {
Header(key string) string // Get request header
SetHeader(key, value string) // Sets response header

Context() context.Context

Request() *http.Request // Request returns the underlying HTTP request.
Response() http.ResponseWriter // Response returns the underlying HTTP response writer.

// SetStatus sets the status code of the response.
// Alias to http.ResponseWriter.WriteHeader.
SetStatus(code int)
Expand Down
97 changes: 55 additions & 42 deletions extra/fuegogin/adaptor.go
Original file line number Diff line number Diff line change
@@ -1,59 +1,76 @@
package fuegogin

import (
"log/slog"

"github.com/getkin/kin-openapi/openapi3"
"github.com/gin-gonic/gin"

"github.com/go-fuego/fuego"
)

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

func PostGin(s *fuego.OpenAPI, e gin.IRouter, path string, handler gin.HandlerFunc, options ...func(*fuego.BaseRoute)) *fuego.Route[any, any] {
return handleGin(s, e, "POST", path, handler, options...)
}

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

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

func handleFuego[T, B any](openapi *fuego.OpenAPI, e gin.IRouter, method, path string, fuegoHandler func(c ContextWithBody[B]) (T, error), options ...func(*fuego.BaseRoute)) *fuego.Route[T, B] {
baseRoute := NewBaseRoute(method, path, fuegoHandler, openapi, options...)
return handle(openapi, e, &fuego.Route[T, B]{BaseRoute: baseRoute}, GinHandler(fuegoHandler))
}

func handleGin(openapi *fuego.OpenAPI, e gin.IRouter, method, path string, ginHandler gin.HandlerFunc, options ...func(*fuego.BaseRoute)) *fuego.Route[any, any] {
baseRoute := NewBaseRoute(method, path, ginHandler, openapi, options...)
return handle(openapi, e, &fuego.Route[any, any]{BaseRoute: baseRoute}, ginHandler)
}

func Post[T, B any](
s *fuego.OpenAPI,
e *gin.Engine,
path string,
handler func(c *ContextWithBody[B]) (T, error),
options ...func(*fuego.BaseRoute),
) *fuego.Route[T, B] {
return Handle(s, e, "POST", path, handler, options...)
func handle[T, B any](openapi *fuego.OpenAPI, e gin.IRouter, route *fuego.Route[T, B], fuegoHandler gin.HandlerFunc) *fuego.Route[T, B] {
if _, ok := e.(*gin.RouterGroup); ok {
route.Path = e.(*gin.RouterGroup).BasePath() + route.Path
}

e.Handle(route.Method, route.Path, fuegoHandler)

err := route.RegisterOpenAPIOperation(openapi)
if err != nil {
slog.Warn("error documenting openapi operation", "error", err)
}

return route
}

func Handle[T, B any](
openapi *fuego.OpenAPI,
e *gin.Engine,
method,
path string,
handler func(c *ContextWithBody[B]) (T, error),
options ...func(*fuego.BaseRoute),
) *fuego.Route[T, B] {
route := &fuego.Route[T, B]{
BaseRoute: fuego.BaseRoute{
Method: method,
Path: path,
Params: make(map[string]fuego.OpenAPIParam),
FullName: fuego.FuncName(handler),
Operation: openapi3.NewOperation(),
OpenAPI: openapi,
},
func NewBaseRoute(method, path string, handler any, openapi *fuego.OpenAPI, options ...func(*fuego.BaseRoute)) fuego.BaseRoute {
baseRoute := fuego.BaseRoute{
Method: method,
Path: path,
Params: make(map[string]fuego.OpenAPIParam),
FullName: fuego.FuncName(handler),
Operation: openapi3.NewOperation(),
OpenAPI: openapi,
}

for _, o := range options {
o(&route.BaseRoute)
o(&baseRoute)
}

route.BaseRoute.GenerateDefaultDescription()
return baseRoute
}

e.Handle(method, path, func(c *gin.Context) {
context := &ContextWithBody[B]{
// Convert a Fuego handler to a Gin handler.
func GinHandler[B, T any](handler func(c ContextWithBody[B]) (T, error)) gin.HandlerFunc {
return func(c *gin.Context) {
context := &contextWithBody[B]{
ginCtx: c,
}

Expand All @@ -69,9 +86,5 @@ func Handle[T, B any](
}

c.JSON(200, resp)
})

route.RegisterOpenAPIOperation(openapi)

return route
}
}
3 changes: 2 additions & 1 deletion extra/fuegogin/adaptor_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package fuegogin_test

import (
"net/http"
"net/http/httptest"
"testing"

Expand Down Expand Up @@ -28,6 +29,6 @@ func TestFuegoGin(t *testing.T) {
e.ServeHTTP(w, r)

require.Equal(t, http.StatusOK, w.Code)
require.Equal(t, `{"message":"Hello "}`, w.Body.String())
require.JSONEq(t, `{"message":"Hello "}`, w.Body.String())
})
}
55 changes: 32 additions & 23 deletions extra/fuegogin/context.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package fuegogin

import (
"context"
"net/http"
"net/url"

Expand All @@ -10,36 +9,46 @@ import (
"github.com/go-fuego/fuego"
)

type ContextWithBody[B any] struct {
ginCtx *gin.Context
type ContextWithBody[B any] interface {
fuego.CommonCtx[B]

Request() *http.Request
Response() gin.ResponseWriter

// Original Gin context
Context() *gin.Context
}

type ContextNoBody = ContextWithBody[any]

type contextWithBody[B any] struct {
ginCtx *gin.Context
}

// Body implements fuego.Ctx.
func (c *ContextWithBody[B]) Body() (B, error) {
func (c *contextWithBody[B]) Body() (B, error) {
var body B
err := c.ginCtx.Bind(&body)
return body, err
}

// Context implements fuego.Ctx.
func (c *ContextWithBody[B]) Context() context.Context {
func (c *contextWithBody[B]) Context() *gin.Context {
return c.ginCtx
}

// Cookie implements fuego.Ctx.
func (c *ContextWithBody[B]) Cookie(name string) (*http.Cookie, error) {
func (c *contextWithBody[B]) Cookie(name string) (*http.Cookie, error) {
panic("unimplemented")
}

// Header implements fuego.Ctx.
func (c *ContextWithBody[B]) Header(key string) string {
func (c *contextWithBody[B]) Header(key string) string {
return c.ginCtx.GetHeader(key)
}

// MustBody implements fuego.Ctx.
func (c *ContextWithBody[B]) MustBody() B {
func (c *contextWithBody[B]) MustBody() B {
body, err := c.Body()
if err != nil {
panic(err)
Expand All @@ -48,76 +57,76 @@ func (c *ContextWithBody[B]) MustBody() B {
}

// PathParam implements fuego.Ctx.
func (c *ContextWithBody[B]) PathParam(name string) string {
func (c *contextWithBody[B]) PathParam(name string) string {
return c.ginCtx.Param(name)
}

// QueryParam implements fuego.Ctx.
func (c *ContextWithBody[B]) QueryParam(name string) string {
func (c *contextWithBody[B]) QueryParam(name string) string {
return c.ginCtx.Query(name)
}

// QueryParamArr implements fuego.Ctx.
func (c *ContextWithBody[B]) QueryParamArr(name string) []string {
func (c *contextWithBody[B]) QueryParamArr(name string) []string {
panic("unimplemented")
}

// QueryParamBool implements fuego.Ctx.
func (c *ContextWithBody[B]) QueryParamBool(name string) bool {
func (c *contextWithBody[B]) QueryParamBool(name string) bool {
panic("unimplemented")
}

// QueryParamBoolErr implements fuego.Ctx.
func (c *ContextWithBody[B]) QueryParamBoolErr(name string) (bool, error) {
func (c *contextWithBody[B]) QueryParamBoolErr(name string) (bool, error) {
panic("unimplemented")
}

// QueryParamInt implements fuego.Ctx.
func (c *ContextWithBody[B]) QueryParamInt(name string) int {
func (c *contextWithBody[B]) QueryParamInt(name string) int {
panic("unimplemented")
}

// QueryParamIntErr implements fuego.Ctx.
func (c *ContextWithBody[B]) QueryParamIntErr(name string) (int, error) {
func (c *contextWithBody[B]) QueryParamIntErr(name string) (int, error) {
panic("unimplemented")
}

// QueryParams implements fuego.Ctx.
func (c *ContextWithBody[B]) QueryParams() url.Values {
func (c *contextWithBody[B]) QueryParams() url.Values {
return c.ginCtx.Request.URL.Query()
}

// Redirect implements fuego.Ctx.
func (c *ContextWithBody[B]) Redirect(code int, url string) (any, error) {
func (c *contextWithBody[B]) Redirect(code int, url string) (any, error) {
c.ginCtx.Redirect(code, url)
return nil, nil
}

// Render implements fuego.Ctx.
func (c *ContextWithBody[B]) Render(templateToExecute string, data any, templateGlobsToOverride ...string) (fuego.CtxRenderer, error) {
func (c *contextWithBody[B]) Render(templateToExecute string, data any, templateGlobsToOverride ...string) (fuego.CtxRenderer, error) {
panic("unimplemented")
}

// Request implements fuego.Ctx.
func (c *ContextWithBody[B]) Request() *http.Request {
func (c *contextWithBody[B]) Request() *http.Request {
return c.ginCtx.Request
}

// Response implements fuego.Ctx.
func (c *ContextWithBody[B]) Response() http.ResponseWriter {
func (c *contextWithBody[B]) Response() gin.ResponseWriter {
return c.ginCtx.Writer
}

// SetCookie implements fuego.Ctx.
func (c *ContextWithBody[B]) SetCookie(cookie http.Cookie) {
func (c *contextWithBody[B]) SetCookie(cookie http.Cookie) {
}

// SetHeader implements fuego.Ctx.
func (c *ContextWithBody[B]) SetHeader(key, value string) {
func (c *contextWithBody[B]) SetHeader(key, value string) {
c.ginCtx.Header(key, value)
}

// SetStatus implements fuego.Ctx.
func (c *ContextWithBody[B]) SetStatus(code int) {
func (c *contextWithBody[B]) SetStatus(code int) {
c.ginCtx.Status(code)
}
33 changes: 33 additions & 0 deletions extra/fuegogin/context_mock.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package fuegogin

import (
"net/http"
"net/url"

"github.com/gin-gonic/gin"
)

type ContextTest[B any] struct {
contextWithBody[B]
BodyInjected B
ErrorInjected error

Params url.Values
}

func (c *ContextTest[B]) Body() (B, error) {
return c.BodyInjected, c.ErrorInjected
}

func (c *ContextTest[B]) Request() *http.Request {
return c.ginCtx.Request
}

func (c *ContextTest[B]) Response() gin.ResponseWriter {
return c.ginCtx.Writer
}

// QueryParam implements fuego.Ctx
func (c *ContextTest[B]) QueryParam(key string) string {
return c.Params.Get(key)
}
Loading

0 comments on commit a4861bb

Please sign in to comment.