-
-
Notifications
You must be signed in to change notification settings - Fork 62
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
400 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
|
||
.vscode | ||
.idea | ||
coverage.out | ||
.DS_Store | ||
**/openapi.json | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
package fuegoecho | ||
|
||
import ( | ||
"net/http" | ||
|
||
"github.com/labstack/echo/v4" | ||
|
||
"github.com/go-fuego/fuego" | ||
"github.com/go-fuego/fuego/internal" | ||
) | ||
|
||
type echoIRouter interface { | ||
Add(method, path string, handler echo.HandlerFunc, middleware ...echo.MiddlewareFunc) *echo.Route | ||
} | ||
|
||
func AddEcho(engine *fuego.Engine, echoRouter *echo.Echo, | ||
method, path string, handler echo.HandlerFunc, | ||
options ...func(*fuego.BaseRoute)) *fuego.Route[any, any] { | ||
return handleEcho(engine, echoRouter, method, path, handler, options...) | ||
} | ||
|
||
func GetEcho(engine *fuego.Engine, echoRouter *echo.Echo, | ||
path string, handler echo.HandlerFunc, | ||
options ...func(*fuego.BaseRoute)) *fuego.Route[any, any] { | ||
return handleEcho(engine, echoRouter, http.MethodGet, path, handler, options...) | ||
} | ||
|
||
func PostEcho(engine *fuego.Engine, echoRouter *echo.Echo, | ||
path string, handler echo.HandlerFunc, | ||
options ...func(*fuego.BaseRoute)) *fuego.Route[any, any] { | ||
return handleEcho(engine, echoRouter, http.MethodPost, path, handler, options...) | ||
} | ||
|
||
func PutEcho(engine *fuego.Engine, echoRouter *echo.Echo, | ||
path string, handler echo.HandlerFunc, | ||
options ...func(*fuego.BaseRoute)) *fuego.Route[any, any] { | ||
return handleEcho(engine, echoRouter, http.MethodPut, path, handler, options...) | ||
} | ||
|
||
func PatchEcho(engine *fuego.Engine, echoRouter *echo.Echo, | ||
path string, handler echo.HandlerFunc, | ||
options ...func(*fuego.BaseRoute)) *fuego.Route[any, any] { | ||
return handleEcho(engine, echoRouter, http.MethodPatch, path, handler, options...) | ||
} | ||
|
||
func DeleteEcho(engine *fuego.Engine, echoRouter *echo.Echo, | ||
path string, handler echo.HandlerFunc, | ||
options ...func(*fuego.BaseRoute)) *fuego.Route[any, any] { | ||
return handleEcho(engine, echoRouter, http.MethodDelete, path, handler, options...) | ||
} | ||
|
||
func Add[T, B any](engine *fuego.Engine, echoRouter *echo.Echo, method, path string, handler func(c fuego.ContextWithBody[B]) (T, error), options ...func(*fuego.BaseRoute)) *fuego.Route[T, B] { | ||
return handleFuego(engine, echoRouter, method, path, handler, options...) | ||
} | ||
|
||
func Get[T, B any](engine *fuego.Engine, echoRouter *echo.Echo, path string, handler func(c fuego.ContextWithBody[B]) (T, error), options ...func(*fuego.BaseRoute)) *fuego.Route[T, B] { | ||
return handleFuego(engine, echoRouter, http.MethodGet, path, handler, options...) | ||
} | ||
|
||
func Post[T, B any](engine *fuego.Engine, echoRouter *echo.Echo, path string, handler func(c fuego.ContextWithBody[B]) (T, error), options ...func(*fuego.BaseRoute)) *fuego.Route[T, B] { | ||
return handleFuego(engine, echoRouter, http.MethodPost, path, handler, options...) | ||
} | ||
|
||
func Put[T, B any](engine *fuego.Engine, echoRouter *echo.Echo, path string, handler func(c fuego.ContextWithBody[B]) (T, error), options ...func(*fuego.BaseRoute)) *fuego.Route[T, B] { | ||
return handleFuego(engine, echoRouter, http.MethodPut, path, handler, options...) | ||
} | ||
|
||
func Patch[T, B any](engine *fuego.Engine, echoRouter *echo.Echo, path string, handler func(c fuego.ContextWithBody[B]) (T, error), options ...func(*fuego.BaseRoute)) *fuego.Route[T, B] { | ||
return handleFuego(engine, echoRouter, http.MethodPatch, path, handler, options...) | ||
} | ||
|
||
func Delete[T, B any](engine *fuego.Engine, echoRouter *echo.Echo, path string, handler func(c fuego.ContextWithBody[B]) (T, error), options ...func(*fuego.BaseRoute)) *fuego.Route[T, B] { | ||
return handleFuego(engine, echoRouter, http.MethodDelete, path, handler, options...) | ||
} | ||
|
||
func handleFuego[T, B any](engine *fuego.Engine, echoRouter *echo.Echo, method, path string, fuegoHandler func(c fuego.ContextWithBody[B]) (T, error), options ...func(*fuego.BaseRoute)) *fuego.Route[T, B] { | ||
baseRoute := fuego.NewBaseRoute(method, path, fuegoHandler, engine.OpenAPI, options...) | ||
return fuego.Registers(engine, echoRouteRegisterer[T, B]{ | ||
echoRouter: echoRouter, | ||
route: fuego.Route[T, B]{BaseRoute: baseRoute}, | ||
echoHandler: EchoHandler(engine, fuegoHandler, baseRoute), | ||
}) | ||
} | ||
|
||
func handleEcho(engine *fuego.Engine, echoRouter *echo.Echo, method, path string, echoHandler echo.HandlerFunc, options ...func(*fuego.BaseRoute)) *fuego.Route[any, any] { | ||
baseRoute := fuego.NewBaseRoute(method, path, echoHandler, engine.OpenAPI, options...) | ||
return fuego.Registers(engine, echoRouteRegisterer[any, any]{ | ||
echoRouter: echoRouter, | ||
route: fuego.Route[any, any]{BaseRoute: baseRoute}, | ||
echoHandler: echoHandler, | ||
}) | ||
} | ||
|
||
type echoRouteRegisterer[T, B any] struct { | ||
echoRouter echoIRouter | ||
echoHandler echo.HandlerFunc | ||
route fuego.Route[T, B] | ||
} | ||
|
||
func (a echoRouteRegisterer[T, B]) Register() fuego.Route[T, B] { | ||
route := a.echoRouter.Add(a.route.Method, a.route.Path, a.echoHandler) | ||
a.route.Path = route.Path | ||
return a.route | ||
} | ||
|
||
// Convert a Fuego handler to a Gin handler. | ||
func EchoHandler[B, T any](engine *fuego.Engine, handler func(c fuego.ContextWithBody[B]) (T, error), route fuego.BaseRoute) echo.HandlerFunc { | ||
return func(c echo.Context) error { | ||
context := &echoContext[B]{ | ||
CommonContext: internal.CommonContext[B]{ | ||
CommonCtx: c.Request().Context(), | ||
UrlValues: c.Request().URL.Query(), | ||
OpenAPIParams: route.Params, | ||
}, | ||
echoCtx: c, | ||
} | ||
fuego.Flow(engine, context, handler) | ||
return nil | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
package fuegoecho | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"net/http" | ||
"strings" | ||
|
||
"github.com/go-fuego/fuego" | ||
"github.com/go-fuego/fuego/internal" | ||
"github.com/labstack/echo/v4" | ||
) | ||
|
||
type echoContext[B any] struct { | ||
internal.CommonContext[B] | ||
echoCtx echo.Context | ||
} | ||
|
||
var ( | ||
_ fuego.ContextWithBody[any] = &echoContext[any]{} | ||
_ fuego.ContextFlowable[any] = &echoContext[any]{} | ||
) | ||
|
||
func (c echoContext[B]) Body() (B, error) { | ||
var body B | ||
err := c.echoCtx.Bind(&body) | ||
if err != nil { | ||
return body, err | ||
} | ||
|
||
return fuego.TransformAndValidate(c, body) | ||
} | ||
|
||
func (c echoContext[B]) Context() context.Context { | ||
return c.echoCtx.Request().Context() | ||
} | ||
|
||
func (c echoContext[B]) Cookie(name string) (*http.Cookie, error) { | ||
return c.echoCtx.Request().Cookie(name) | ||
} | ||
|
||
func (c echoContext[B]) Header(key string) string { | ||
return c.echoCtx.Request().Header.Get(key) | ||
} | ||
|
||
func (c echoContext[B]) MustBody() B { | ||
body, err := c.Body() | ||
if err != nil { | ||
panic(err) | ||
} | ||
return body | ||
} | ||
|
||
func (c echoContext[B]) PathParam(name string) string { | ||
return c.echoCtx.Param(name) | ||
} | ||
|
||
func (c echoContext[B]) MainLang() string { | ||
return strings.Split(c.MainLocale(), "-")[0] | ||
} | ||
|
||
func (c echoContext[B]) MainLocale() string { | ||
return strings.Split(c.Request().Header.Get("Accept-Language"), ",")[0] | ||
} | ||
|
||
func (c echoContext[B]) Redirect(code int, url string) (any, error) { | ||
c.echoCtx.Redirect(code, url) | ||
return nil, nil | ||
} | ||
|
||
func (c echoContext[B]) Render(templateToExecute string, data any, templateGlobsToOverride ...string) (fuego.CtxRenderer, error) { | ||
panic("unimplemented") | ||
} | ||
|
||
func (c echoContext[B]) Request() *http.Request { | ||
return c.echoCtx.Request() | ||
} | ||
|
||
func (c echoContext[B]) Response() http.ResponseWriter { | ||
return c.echoCtx.Response() | ||
} | ||
|
||
func (c echoContext[B]) SetCookie(cookie http.Cookie) { | ||
c.echoCtx.SetCookie(&cookie) | ||
} | ||
|
||
func (c echoContext[B]) HasCookie(name string) bool { | ||
_, err := c.Cookie(name) | ||
return err == nil | ||
} | ||
|
||
func (c echoContext[B]) HasHeader(key string) bool { | ||
_, ok := c.echoCtx.Request().Header[key] | ||
return ok | ||
} | ||
|
||
func (c echoContext[B]) SetHeader(key, value string) { | ||
c.echoCtx.Response().Header().Add(key, value) | ||
} | ||
|
||
func (c echoContext[B]) SetStatus(code int) { | ||
c.echoCtx.Response().WriteHeader(code) | ||
} | ||
|
||
func (c echoContext[B]) Serialize(data any) error { | ||
status := c.echoCtx.Response().Status | ||
if status == 0 { | ||
status = c.DefaultStatusCode | ||
} | ||
if status == 0 { | ||
status = http.StatusOK | ||
} | ||
c.echoCtx.JSON(status, data) | ||
return nil | ||
} | ||
|
||
func (c echoContext[B]) SerializeError(err error) { | ||
statusCode := http.StatusInternalServerError | ||
var errorWithStatusCode fuego.ErrorWithStatus | ||
if errors.As(err, &errorWithStatusCode) { | ||
statusCode = errorWithStatusCode.StatusCode() | ||
} | ||
c.echoCtx.JSON(statusCode, err) | ||
} | ||
|
||
func (c echoContext[B]) SetDefaultStatusCode() { | ||
if c.DefaultStatusCode == 0 { | ||
c.DefaultStatusCode = http.StatusOK | ||
} | ||
c.SetStatus(c.DefaultStatusCode) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
package fuegoecho | ||
|
||
import ( | ||
"net/http" | ||
"net/url" | ||
) | ||
|
||
type ContextTest[B any] struct { | ||
echoContext[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.echoCtx.Request() | ||
} | ||
|
||
func (c *ContextTest[B]) Response() http.ResponseWriter { | ||
return c.echoCtx.Response().Writer | ||
} | ||
|
||
// QueryParam implements fuego.Ctx | ||
func (c *ContextTest[B]) QueryParam(key string) string { | ||
return c.Params.Get(key) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
module github.com/go-fuego/fuego/extra/fuegoecho | ||
|
||
go 1.23.3 | ||
|
||
require ( | ||
github.com/go-fuego/fuego v0.17.0 | ||
github.com/labstack/echo/v4 v4.13.3 | ||
) | ||
|
||
require ( | ||
github.com/gabriel-vasile/mimetype v1.4.7 // indirect | ||
github.com/getkin/kin-openapi v0.128.0 // indirect | ||
github.com/go-openapi/jsonpointer v0.21.0 // indirect | ||
github.com/go-openapi/swag v0.23.0 // indirect | ||
github.com/go-playground/locales v0.14.1 // indirect | ||
github.com/go-playground/universal-translator v0.18.1 // indirect | ||
github.com/go-playground/validator/v10 v10.23.0 // indirect | ||
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect | ||
github.com/gorilla/schema v1.4.1 // indirect | ||
github.com/invopop/yaml v0.3.1 // indirect | ||
github.com/josharian/intern v1.0.0 // indirect | ||
github.com/labstack/gommon v0.4.2 // indirect | ||
github.com/leodido/go-urn v1.4.0 // indirect | ||
github.com/mailru/easyjson v0.9.0 // indirect | ||
github.com/mattn/go-colorable v0.1.13 // indirect | ||
github.com/mattn/go-isatty v0.0.20 // indirect | ||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect | ||
github.com/perimeterx/marshmallow v1.1.5 // indirect | ||
github.com/ugorji/go/codec v1.2.12 // indirect | ||
github.com/valyala/bytebufferpool v1.0.0 // indirect | ||
github.com/valyala/fasttemplate v1.2.2 // indirect | ||
golang.org/x/crypto v0.31.0 // indirect | ||
golang.org/x/net v0.33.0 // indirect | ||
golang.org/x/sys v0.28.0 // indirect | ||
golang.org/x/text v0.21.0 // indirect | ||
gopkg.in/yaml.v3 v3.0.1 // indirect | ||
) |
Oops, something went wrong.