diff --git a/ctx.go b/ctx.go index 63c1e90b..d50f862e 100644 --- a/ctx.go +++ b/ctx.go @@ -47,9 +47,6 @@ type ctx[B any] interface { QueryParamBoolErr(name string) (bool, error) QueryParams() url.Values - MainLang() string // ex: fr. MainLang returns the main language of the request. It is the first language of the Accept-Language header. To get the main locale (ex: fr-CA), use [Ctx.MainLocale]. - MainLocale() string // ex: en-US. MainLocale returns the main locale of the request. It is the first locale of the Accept-Language header. To get the main language (ex: en), use [Ctx.MainLang]. - // Render renders the given templates with the given data. // Example: // fuego.Get(s, "/recipes", func(c fuego.ContextNoBody) (any, error) { diff --git a/extra/fuegogin/adaptor.go b/extra/fuegogin/adaptor.go index a64d72d3..b2df568b 100644 --- a/extra/fuegogin/adaptor.go +++ b/extra/fuegogin/adaptor.go @@ -1,50 +1,76 @@ package fuegogin import ( + "github.com/getkin/kin-openapi/openapi3" "github.com/gin-gonic/gin" "github.com/go-fuego/fuego" ) func Get[T, B any]( - s *fuego.Server, + s *fuego.OpenAPI, e *gin.Engine, path string, handler func(c *ContextWithBody[T]) (B, error), options ...func(*fuego.BaseRoute), ) *fuego.Route[B, T] { - return Handle(s, e, "GET", path, handler) + return Handle(s, e, "GET", path, handler, options...) } func Post[T, B any]( - s *fuego.Server, + s *fuego.OpenAPI, e *gin.Engine, path string, handler func(c *ContextWithBody[T]) (B, error), options ...func(*fuego.BaseRoute), ) *fuego.Route[B, T] { - return Handle(s, e, "GET", path, handler) + return Handle(s, e, "POST", path, handler, options...) } func Handle[T, B any]( - s *fuego.Server, + openapi *fuego.OpenAPI, e *gin.Engine, method, path string, handler func(c *ContextWithBody[T]) (B, error), options ...func(*fuego.BaseRoute), ) *fuego.Route[B, T] { + route := &fuego.Route[B, T]{ + 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) + } + e.Handle(method, path, func(c *gin.Context) { - ans, err := handler(&ContextWithBody[T]{}) + context := &ContextWithBody[T]{ + ContextNoBody: ContextNoBody{ + ginCtx: c, + }, + } + ans, err := handler(context) if err != nil { c.Error(err) return } + if c.Request.Header.Get("Accept") == "application/xml" { + c.XML(200, ans) + return + } + c.JSON(200, ans) }) - // Also register the route with fuego!!! - // Useful for the OpenAPI spec but also allows for to run Fuego in parallel. - return fuego.Get(s, path, handler, options...) + route.RegisterOpenAPIOperation(openapi) + + return route } diff --git a/extra/fuegogin/adaptor_test.go b/extra/fuegogin/adaptor_test.go index 69a50261..2462ad25 100644 --- a/extra/fuegogin/adaptor_test.go +++ b/extra/fuegogin/adaptor_test.go @@ -9,7 +9,7 @@ import ( "github.com/go-fuego/fuego/extra/fuegogin/lib" ) -func TestXxx(t *testing.T) { +func TestFuegoGin(t *testing.T) { e, _ := lib.SetupGin() t.Run("simply test gin", func(t *testing.T) { diff --git a/extra/fuegogin/context.go b/extra/fuegogin/context.go index acda03d1..272a8fb4 100644 --- a/extra/fuegogin/context.go +++ b/extra/fuegogin/context.go @@ -2,22 +2,35 @@ package fuegogin import ( "context" + "fmt" "net/http" "net/url" + "github.com/gin-gonic/gin" + "github.com/go-fuego/fuego" ) -type ContextWithBody[B any] struct{} +type ContextWithBody[B any] struct { + ContextNoBody +} + +type ContextNoBody struct { + fuego.ContextNoBody + ginCtx *gin.Context +} // Body implements fuego.Ctx. func (c *ContextWithBody[B]) Body() (B, error) { - panic("unimplemented") + fmt.Println("c.ginCtx", c.ginCtx) + var body B + err := c.ginCtx.Bind(&body) + return body, err } // Context implements fuego.Ctx. func (c *ContextWithBody[B]) Context() context.Context { - panic("unimplemented") + return c.ginCtx } // Cookie implements fuego.Ctx. @@ -27,32 +40,26 @@ func (c *ContextWithBody[B]) Cookie(name string) (*http.Cookie, error) { // Header implements fuego.Ctx. func (c *ContextWithBody[B]) Header(key string) string { - panic("unimplemented") -} - -// MainLang implements fuego.Ctx. -func (c *ContextWithBody[B]) MainLang() string { - panic("unimplemented") -} - -// MainLocale implements fuego.Ctx. -func (c *ContextWithBody[B]) MainLocale() string { - panic("unimplemented") + return c.ginCtx.GetHeader(key) } // MustBody implements fuego.Ctx. func (c *ContextWithBody[B]) MustBody() B { - panic("unimplemented") + body, err := c.Body() + if err != nil { + panic(err) + } + return body } // PathParam implements fuego.Ctx. func (c *ContextWithBody[B]) PathParam(name string) string { - panic("unimplemented") + return c.ginCtx.Param(name) } // QueryParam implements fuego.Ctx. func (c *ContextWithBody[B]) QueryParam(name string) string { - panic("unimplemented") + return c.ginCtx.Query(name) } // QueryParamArr implements fuego.Ctx. @@ -82,12 +89,13 @@ func (c *ContextWithBody[B]) QueryParamIntErr(name string) (int, error) { // QueryParams implements fuego.Ctx. func (c *ContextWithBody[B]) QueryParams() url.Values { - panic("unimplemented") + return c.ginCtx.Request.URL.Query() } // Redirect implements fuego.Ctx. func (c *ContextWithBody[B]) Redirect(code int, url string) (any, error) { - panic("unimplemented") + c.ginCtx.Redirect(code, url) + return nil, nil } // Render implements fuego.Ctx. @@ -97,25 +105,24 @@ func (c *ContextWithBody[B]) Render(templateToExecute string, data any, template // Request implements fuego.Ctx. func (c *ContextWithBody[B]) Request() *http.Request { - panic("unimplemented") + return c.ginCtx.Request } // Response implements fuego.Ctx. func (c *ContextWithBody[B]) Response() http.ResponseWriter { - panic("unimplemented") + return c.ginCtx.Writer } // SetCookie implements fuego.Ctx. func (c *ContextWithBody[B]) SetCookie(cookie http.Cookie) { - panic("unimplemented") } // SetHeader implements fuego.Ctx. func (c *ContextWithBody[B]) SetHeader(key, value string) { - panic("unimplemented") + c.ginCtx.Header(key, value) } // SetStatus implements fuego.Ctx. func (c *ContextWithBody[B]) SetStatus(code int) { - panic("unimplemented") + c.ginCtx.Status(code) } diff --git a/extra/fuegogin/go.mod b/extra/fuegogin/go.mod index c860c21a..0d21b062 100644 --- a/extra/fuegogin/go.mod +++ b/extra/fuegogin/go.mod @@ -1,8 +1,9 @@ module github.com/go-fuego/fuego/extra/fuegogin -go 1.22.5 +go 1.23.0 require ( + github.com/getkin/kin-openapi v0.128.0 github.com/gin-gonic/gin v1.10.0 github.com/go-fuego/fuego v0.16.1 github.com/stretchr/testify v1.10.0 @@ -15,7 +16,6 @@ require ( github.com/cloudwego/iasm v0.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/gabriel-vasile/mimetype v1.4.7 // indirect - github.com/getkin/kin-openapi v0.128.0 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect diff --git a/extra/fuegogin/lib/lib.go b/extra/fuegogin/lib/lib.go index 4c7e1478..78f2db25 100644 --- a/extra/fuegogin/lib/lib.go +++ b/extra/fuegogin/lib/lib.go @@ -1,10 +1,15 @@ package lib import ( + "fmt" + "net/http" + "github.com/gin-gonic/gin" "github.com/go-fuego/fuego" "github.com/go-fuego/fuego/extra/fuegogin" + "github.com/go-fuego/fuego/option" + "github.com/go-fuego/fuego/param" ) type HelloRequest struct { @@ -15,14 +20,30 @@ type HelloResponse struct { Message string `json:"message"` } -func SetupGin() (*gin.Engine, *fuego.Server) { +func SetupGin() (*gin.Engine, *fuego.OpenAPI) { e := gin.Default() - s := fuego.NewServer() + openapi := fuego.NewOpenAPI() + // Register Gin controller e.GET("/gin", ginController) - fuegogin.Get(s, e, "/fuego", fuegoController) - return e, s + // 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, + option.Description("Some description"), + option.OperationID("SomeOperationID"), + option.AddError(409, "Name Already Exists"), + option.DefaultStatusCode(201), + option.Query("name", "Your name", param.Example("name example", "John Carmack")), + option.Header("X-Request-ID", "Request ID", param.Default("123456")), + option.Header("Content-Type", "Content Type", param.Default("application/json")), + ) + + // Serve the OpenAPI spec + e.GET("/openapi.json", serveController(openapi)) + e.GET("/swagger", DefaultOpenAPIHandler("/openapi.json")) + + return e, openapi } func ginController(c *gin.Context) { @@ -30,7 +51,29 @@ func ginController(c *gin.Context) { } func fuegoController(c *fuegogin.ContextWithBody[HelloRequest]) (HelloResponse, error) { + body, err := c.Body() + if err != nil { + return HelloResponse{}, err + } + fmt.Println("body", body) + + name := c.QueryParam("name") + fmt.Println("name", name) + return HelloResponse{ - Message: "Hello ", + Message: "Hello " + body.Name, }, nil } + +func serveController(s *fuego.OpenAPI) func(ctx *gin.Context) { + return func(ctx *gin.Context) { + ctx.JSON(http.StatusOK, s.Description()) + } +} + +func DefaultOpenAPIHandler(specURL string) gin.HandlerFunc { + return func(ctx *gin.Context) { + ctx.Header("Content-Type", "text/html; charset=utf-8") + ctx.String(200, fuego.DefaultOpenAPIHTML(specURL)) + } +} diff --git a/extra/fuegogin/serve/main.go b/extra/fuegogin/serve/main.go index 0bceb7ab..692a8cb6 100644 --- a/extra/fuegogin/serve/main.go +++ b/extra/fuegogin/serve/main.go @@ -1,15 +1,13 @@ package main -import "github.com/go-fuego/fuego/extra/fuegogin/lib" +import ( + "github.com/go-fuego/fuego/extra/fuegogin/lib" +) func main() { - e, s := lib.SetupGin() + e, _ := lib.SetupGin() - go func() { - s.Run() - }() - - err := e.Run(":8080") + err := e.Run(":8980") if err != nil { panic(err) } diff --git a/openapi.go b/openapi.go index b379830c..1559a02e 100644 --- a/openapi.go +++ b/openapi.go @@ -285,6 +285,12 @@ func RegisterOpenAPIOperation[T, B any](openapi *OpenAPI, route Route[T, B]) (*o } } + // Names & default description + + if route.Operation.OperationID == "" { + route.Operation.OperationID = route.Method + "_" + strings.ReplaceAll(strings.ReplaceAll(route.Path, "{", ":"), "}", "") + } + openapi.Description().AddOperation(route.Path, route.Method, route.Operation) return route.Operation, nil