Compare commits

...

10 Commits

Author SHA1 Message Date
15a4b2a713 v0.0.209 removed g context from err func 2023-07-25 10:56:03 +02:00
493c6ebae8 v0.0.208 remove context from err functions because its not used 2023-07-25 10:51:14 +02:00
fb847b03af v0.0.207 renamed APIError to Error 2023-07-25 10:47:00 +02:00
f826633e6e v0.0.206 2023-07-24 18:50:14 +02:00
edeae23bf1 v0.0.205 2023-07-24 18:47:48 +02:00
a038b86147 v0.0.204 2023-07-24 18:42:33 +02:00
ede0b99d3a v0.0.203 2023-07-24 18:38:04 +02:00
d04ce18eb0 v0.0.202 2023-07-24 18:34:56 +02:00
8ae9a0f107 v0.0.201 2023-07-24 18:22:36 +02:00
a259bb6dbc v0.0.200 2023-07-24 17:42:18 +02:00
7 changed files with 196 additions and 26 deletions

View File

@@ -47,6 +47,7 @@ var (
TypeBindFailQuery = ErrorType{"BINDFAIL_QUERY", langext.Ptr(400)} TypeBindFailQuery = ErrorType{"BINDFAIL_QUERY", langext.Ptr(400)}
TypeBindFailJSON = ErrorType{"BINDFAIL_JSON", langext.Ptr(400)} TypeBindFailJSON = ErrorType{"BINDFAIL_JSON", langext.Ptr(400)}
TypeBindFailFormData = ErrorType{"BINDFAIL_FORMDATA", langext.Ptr(400)} TypeBindFailFormData = ErrorType{"BINDFAIL_FORMDATA", langext.Ptr(400)}
TypeBindFailHeader = ErrorType{"BINDFAIL_HEADER", langext.Ptr(400)}
TypeUnauthorized = ErrorType{"UNAUTHORIZED", langext.Ptr(401)} TypeUnauthorized = ErrorType{"UNAUTHORIZED", langext.Ptr(401)}
TypeAuthFailed = ErrorType{"AUTH_FAILED", langext.Ptr(401)} TypeAuthFailed = ErrorType{"AUTH_FAILED", langext.Ptr(401)}

View File

@@ -1,8 +1,14 @@
package ginext package ginext
import ( import (
"fmt"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/rs/zerolog/log"
"gogs.mikescher.com/BlackForestBytes/goext/langext"
"gogs.mikescher.com/BlackForestBytes/goext/mathext"
"net"
"net/http" "net/http"
"strings"
"time" "time"
) )
@@ -13,6 +19,15 @@ type GinWrapper struct {
allowCors bool allowCors bool
ginDebug bool ginDebug bool
requestTimeout time.Duration requestTimeout time.Duration
routeSpecs []ginRouteSpec
}
type ginRouteSpec struct {
Method string
URL string
Middlewares []string
Handler string
} }
func NewEngine(allowCors bool, ginDebug bool, timeout time.Duration) *GinWrapper { func NewEngine(allowCors bool, ginDebug bool, timeout time.Duration) *GinWrapper {
@@ -33,18 +48,94 @@ func NewEngine(allowCors bool, ginDebug bool, timeout time.Duration) *GinWrapper
engine.Use(CorsMiddleware()) engine.Use(CorsMiddleware())
} }
// do not debug-print routes
gin.DebugPrintRouteFunc = func(_, _, _ string, _ int) {}
if ginDebug { if ginDebug {
gin.SetMode(gin.ReleaseMode)
ginlogger := gin.Logger() ginlogger := gin.Logger()
engine.Use(func(context *gin.Context) { engine.Use(func(context *gin.Context) {
if !wrapper.SuppressGinLogs { if !wrapper.SuppressGinLogs {
ginlogger(context) ginlogger(context)
} }
}) })
} else {
gin.SetMode(gin.DebugMode)
} }
return wrapper return wrapper
} }
func (w *GinWrapper) ServeHTTP(writer http.ResponseWriter, request *http.Request) { func (w *GinWrapper) ListenAndServeHTTP(addr string, postInit func(port string)) (chan error, *http.Server) {
w.engine.ServeHTTP(writer, request)
w.DebugPrintRoutes()
httpserver := &http.Server{
Addr: addr,
Handler: w.engine,
}
errChan := make(chan error)
go func() {
ln, err := net.Listen("tcp", httpserver.Addr)
if err != nil {
errChan <- err
return
}
_, port, err := net.SplitHostPort(ln.Addr().String())
if err != nil {
errChan <- err
return
}
log.Info().Str("address", httpserver.Addr).Msg("HTTP-Server started on http://localhost:" + port)
if postInit != nil {
postInit(port) // the net.Listener a few lines above is at this point actually already buffering requests
}
errChan <- httpserver.Serve(ln)
}()
return errChan, httpserver
}
func (w *GinWrapper) DebugPrintRoutes() {
if !w.ginDebug {
return
}
lines := make([][4]string, 0)
pad := [4]int{0, 0, 0, 0}
for _, spec := range w.routeSpecs {
line := [4]string{
spec.Method,
spec.URL,
strings.Join(spec.Middlewares, " -> "),
spec.Handler,
}
lines = append(lines, line)
pad[0] = mathext.Max(pad[0], len(line[0]))
pad[1] = mathext.Max(pad[1], len(line[1]))
pad[2] = mathext.Max(pad[2], len(line[2]))
pad[3] = mathext.Max(pad[3], len(line[3]))
}
for _, line := range lines {
fmt.Printf("Gin-Route: %s %s --> %s --> %s\n",
langext.StrPadRight("["+line[0]+"]", " ", pad[0]+2),
langext.StrPadRight(line[1], " ", pad[1]),
langext.StrPadRight(line[2], " ", pad[2]),
langext.StrPadRight(line[3], " ", pad[3]))
}
} }

View File

@@ -25,7 +25,7 @@ func Wrap(w *GinWrapper, fn WHandlerFunc) gin.HandlerFunc {
Str("trace", stackTrace). Str("trace", stackTrace).
Build() Build()
wrap = APIError(g, err) wrap = Error(err)
} }
if g.Writer.Written() { if g.Writer.Written() {

View File

@@ -17,6 +17,7 @@ type PreContext struct {
query any query any
body any body any
form any form any
header any
} }
func (pctx *PreContext) URI(uri any) *PreContext { func (pctx *PreContext) URI(uri any) *PreContext {
@@ -39,6 +40,11 @@ func (pctx *PreContext) Form(form any) *PreContext {
return pctx return pctx
} }
func (pctx *PreContext) Header(header any) *PreContext {
pctx.header = header
return pctx
}
func (pctx PreContext) Start() (*AppContext, *gin.Context, *HTTPResponse) { func (pctx PreContext) Start() (*AppContext, *gin.Context, *HTTPResponse) {
if pctx.uri != nil { if pctx.uri != nil {
if err := pctx.ginCtx.ShouldBindUri(pctx.uri); err != nil { if err := pctx.ginCtx.ShouldBindUri(pctx.uri); err != nil {
@@ -46,7 +52,7 @@ func (pctx PreContext) Start() (*AppContext, *gin.Context, *HTTPResponse) {
WithType(exerr.TypeBindFailURI). WithType(exerr.TypeBindFailURI).
Str("struct_type", fmt.Sprintf("%T", pctx.uri)). Str("struct_type", fmt.Sprintf("%T", pctx.uri)).
Build() Build()
return nil, nil, langext.Ptr(APIError(pctx.ginCtx, err)) return nil, nil, langext.Ptr(Error(err))
} }
} }
@@ -56,7 +62,7 @@ func (pctx PreContext) Start() (*AppContext, *gin.Context, *HTTPResponse) {
WithType(exerr.TypeBindFailQuery). WithType(exerr.TypeBindFailQuery).
Str("struct_type", fmt.Sprintf("%T", pctx.query)). Str("struct_type", fmt.Sprintf("%T", pctx.query)).
Build() Build()
return nil, nil, langext.Ptr(APIError(pctx.ginCtx, err)) return nil, nil, langext.Ptr(Error(err))
} }
} }
@@ -67,13 +73,13 @@ func (pctx PreContext) Start() (*AppContext, *gin.Context, *HTTPResponse) {
WithType(exerr.TypeBindFailJSON). WithType(exerr.TypeBindFailJSON).
Str("struct_type", fmt.Sprintf("%T", pctx.body)). Str("struct_type", fmt.Sprintf("%T", pctx.body)).
Build() Build()
return nil, nil, langext.Ptr(APIError(pctx.ginCtx, err)) return nil, nil, langext.Ptr(Error(err))
} }
} else { } else {
err := exerr.New(exerr.TypeBindFailJSON, "missing JSON body"). err := exerr.New(exerr.TypeBindFailJSON, "missing JSON body").
Str("struct_type", fmt.Sprintf("%T", pctx.body)). Str("struct_type", fmt.Sprintf("%T", pctx.body)).
Build() Build()
return nil, nil, langext.Ptr(APIError(pctx.ginCtx, err)) return nil, nil, langext.Ptr(Error(err))
} }
} }
@@ -84,13 +90,23 @@ func (pctx PreContext) Start() (*AppContext, *gin.Context, *HTTPResponse) {
WithType(exerr.TypeBindFailFormData). WithType(exerr.TypeBindFailFormData).
Str("struct_type", fmt.Sprintf("%T", pctx.form)). Str("struct_type", fmt.Sprintf("%T", pctx.form)).
Build() Build()
return nil, nil, langext.Ptr(APIError(pctx.ginCtx, err)) return nil, nil, langext.Ptr(Error(err))
} }
} else { } else {
err := exerr.New(exerr.TypeBindFailFormData, "missing form body"). err := exerr.New(exerr.TypeBindFailFormData, "missing form body").
Str("struct_type", fmt.Sprintf("%T", pctx.form)). Str("struct_type", fmt.Sprintf("%T", pctx.form)).
Build() Build()
return nil, nil, langext.Ptr(APIError(pctx.ginCtx, err)) return nil, nil, langext.Ptr(Error(err))
}
}
if pctx.header != nil {
if err := pctx.ginCtx.ShouldBindHeader(pctx.query); err != nil {
err = exerr.Wrap(err, "Failed to read header").
WithType(exerr.TypeBindFailHeader).
Str("struct_type", fmt.Sprintf("%T", pctx.query)).
Build()
return nil, nil, langext.Ptr(Error(err))
} }
} }

View File

@@ -170,12 +170,18 @@ func Redirect(sc int, newURL string) HTTPResponse {
return &redirectHTTPResponse{statusCode: sc, url: newURL} return &redirectHTTPResponse{statusCode: sc, url: newURL}
} }
func APIError(g *gin.Context, e error) HTTPResponse { func Error(e error) HTTPResponse {
return &jsonAPIErrResponse{ return &jsonAPIErrResponse{
err: exerr.FromError(e), err: exerr.FromError(e),
} }
} }
func NotImplemented(g *gin.Context) HTTPResponse { func ErrWrap(e error, errorType exerr.ErrorType, msg string) HTTPResponse {
return APIError(g, exerr.New(exerr.TypeNotImplemented, "").Build()) return &jsonAPIErrResponse{
err: exerr.FromError(exerr.Wrap(e, msg).WithType(errorType).Build()),
}
}
func NotImplemented() HTTPResponse {
return Error(exerr.New(exerr.TypeNotImplemented, "").Build())
} }

View File

@@ -3,7 +3,12 @@ package ginext
import ( import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"gogs.mikescher.com/BlackForestBytes/goext/langext" "gogs.mikescher.com/BlackForestBytes/goext/langext"
"gogs.mikescher.com/BlackForestBytes/goext/rext"
"net/http" "net/http"
"reflect"
"regexp"
"runtime"
"strings"
) )
var anyMethods = []string{ var anyMethods = []string{
@@ -21,7 +26,7 @@ type GinRoutesWrapper struct {
type GinRouteBuilder struct { type GinRouteBuilder struct {
routes *GinRoutesWrapper routes *GinRoutesWrapper
methods []string method string
relPath string relPath string
handlers []gin.HandlerFunc handlers []gin.HandlerFunc
} }
@@ -41,39 +46,39 @@ func (w *GinRoutesWrapper) Use(middleware ...gin.HandlerFunc) *GinRoutesWrapper
} }
func (w *GinRoutesWrapper) GET(relativePath string) *GinRouteBuilder { func (w *GinRoutesWrapper) GET(relativePath string) *GinRouteBuilder {
return &GinRouteBuilder{routes: w, methods: []string{http.MethodGet}, relPath: relativePath, handlers: langext.ArrCopy(w.defaultHandler)} return &GinRouteBuilder{routes: w, method: http.MethodGet, relPath: relativePath, handlers: langext.ArrCopy(w.defaultHandler)}
} }
func (w *GinRoutesWrapper) POST(relativePath string) *GinRouteBuilder { func (w *GinRoutesWrapper) POST(relativePath string) *GinRouteBuilder {
return &GinRouteBuilder{routes: w, methods: []string{http.MethodPost}, relPath: relativePath, handlers: langext.ArrCopy(w.defaultHandler)} return &GinRouteBuilder{routes: w, method: http.MethodPost, relPath: relativePath, handlers: langext.ArrCopy(w.defaultHandler)}
} }
func (w *GinRoutesWrapper) DELETE(relativePath string) *GinRouteBuilder { func (w *GinRoutesWrapper) DELETE(relativePath string) *GinRouteBuilder {
return &GinRouteBuilder{routes: w, methods: []string{http.MethodDelete}, relPath: relativePath, handlers: langext.ArrCopy(w.defaultHandler)} return &GinRouteBuilder{routes: w, method: http.MethodDelete, relPath: relativePath, handlers: langext.ArrCopy(w.defaultHandler)}
} }
func (w *GinRoutesWrapper) PATCH(relativePath string) *GinRouteBuilder { func (w *GinRoutesWrapper) PATCH(relativePath string) *GinRouteBuilder {
return &GinRouteBuilder{routes: w, methods: []string{http.MethodPatch}, relPath: relativePath, handlers: langext.ArrCopy(w.defaultHandler)} return &GinRouteBuilder{routes: w, method: http.MethodPatch, relPath: relativePath, handlers: langext.ArrCopy(w.defaultHandler)}
} }
func (w *GinRoutesWrapper) PUT(relativePath string) *GinRouteBuilder { func (w *GinRoutesWrapper) PUT(relativePath string) *GinRouteBuilder {
return &GinRouteBuilder{routes: w, methods: []string{http.MethodPut}, relPath: relativePath, handlers: langext.ArrCopy(w.defaultHandler)} return &GinRouteBuilder{routes: w, method: http.MethodPut, relPath: relativePath, handlers: langext.ArrCopy(w.defaultHandler)}
} }
func (w *GinRoutesWrapper) OPTIONS(relativePath string) *GinRouteBuilder { func (w *GinRoutesWrapper) OPTIONS(relativePath string) *GinRouteBuilder {
return &GinRouteBuilder{routes: w, methods: []string{http.MethodOptions}, relPath: relativePath, handlers: langext.ArrCopy(w.defaultHandler)} return &GinRouteBuilder{routes: w, method: http.MethodOptions, relPath: relativePath, handlers: langext.ArrCopy(w.defaultHandler)}
} }
func (w *GinRoutesWrapper) HEAD(relativePath string) *GinRouteBuilder { func (w *GinRoutesWrapper) HEAD(relativePath string) *GinRouteBuilder {
return &GinRouteBuilder{routes: w, methods: []string{http.MethodHead}, relPath: relativePath, handlers: langext.ArrCopy(w.defaultHandler)} return &GinRouteBuilder{routes: w, method: http.MethodHead, relPath: relativePath, handlers: langext.ArrCopy(w.defaultHandler)}
} }
func (w *GinRoutesWrapper) COUNT(relativePath string) *GinRouteBuilder { func (w *GinRoutesWrapper) COUNT(relativePath string) *GinRouteBuilder {
return &GinRouteBuilder{routes: w, methods: []string{"COUNT"}, relPath: relativePath, handlers: langext.ArrCopy(w.defaultHandler)} return &GinRouteBuilder{routes: w, method: "COUNT", relPath: relativePath, handlers: langext.ArrCopy(w.defaultHandler)}
} }
func (w *GinRoutesWrapper) Any(relativePath string) *GinRouteBuilder { func (w *GinRoutesWrapper) Any(relativePath string) *GinRouteBuilder {
return &GinRouteBuilder{routes: w, methods: anyMethods, relPath: relativePath, handlers: langext.ArrCopy(w.defaultHandler)} return &GinRouteBuilder{routes: w, method: "*", relPath: relativePath, handlers: langext.ArrCopy(w.defaultHandler)}
} }
func (w *GinRouteBuilder) Use(middleware ...gin.HandlerFunc) *GinRouteBuilder { func (w *GinRouteBuilder) Use(middleware ...gin.HandlerFunc) *GinRouteBuilder {
@@ -82,12 +87,63 @@ func (w *GinRouteBuilder) Use(middleware ...gin.HandlerFunc) *GinRouteBuilder {
} }
func (w *GinRouteBuilder) Handle(handler WHandlerFunc) { func (w *GinRouteBuilder) Handle(handler WHandlerFunc) {
middlewareNames := langext.ArrMap(w.handlers, func(v gin.HandlerFunc) string { return nameOfFunction(v) })
handlerName := nameOfFunction(handler)
w.handlers = append(w.handlers, Wrap(w.routes.wrapper, handler)) w.handlers = append(w.handlers, Wrap(w.routes.wrapper, handler))
for _, m := range w.methods {
w.routes.routes.Handle(m, w.relPath, w.handlers...) methodName := w.method
if w.method == "*" {
methodName = "ANY"
for _, method := range anyMethods {
w.routes.routes.Handle(method, w.relPath, w.handlers...)
}
} else {
w.routes.routes.Handle(w.method, w.relPath, w.handlers...)
} }
w.routes.wrapper.routeSpecs = append(w.routes.wrapper.routeSpecs, ginRouteSpec{
Method: methodName,
URL: w.relPath,
Middlewares: middlewareNames,
Handler: handlerName,
})
} }
func (w *GinWrapper) NoRoute(handler WHandlerFunc) { func (w *GinWrapper) NoRoute(handler WHandlerFunc) {
w.engine.NoRoute(Wrap(w, handler)) w.engine.NoRoute(Wrap(w, handler))
w.routeSpecs = append(w.routeSpecs, ginRouteSpec{
Method: "ANY",
URL: "[NO_ROUTE]",
Middlewares: nil,
Handler: nameOfFunction(handler),
})
}
func nameOfFunction(f any) string {
fname := runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name()
split := strings.Split(fname, "/")
if len(split) == 0 {
return ""
}
fname = split[len(split)-1]
// https://stackoverflow.com/a/32925345/1761622
if strings.HasSuffix(fname, "-fm") {
fname = fname[:len(fname)-len("-fm")]
}
suffix := rext.W(regexp.MustCompile("\\.func[0-9]+$"))
if match, ok := suffix.MatchFirst(fname); ok {
fname = fname[:len(fname)-match.FullMatch().Length()]
}
return fname
} }

View File

@@ -1,5 +1,5 @@
package goext package goext
const GoextVersion = "0.0.199" const GoextVersion = "0.0.209"
const GoextVersionTimestamp = "2023-07-24T17:23:38+0200" const GoextVersionTimestamp = "2023-07-25T10:56:03+0200"