package ginext import ( "bytes" "context" "fmt" "github.com/gin-gonic/gin" "github.com/gin-gonic/gin/binding" "gogs.mikescher.com/BlackForestBytes/goext/dataext" "gogs.mikescher.com/BlackForestBytes/goext/exerr" "gogs.mikescher.com/BlackForestBytes/goext/langext" "io" "runtime/debug" "time" ) type PreContext struct { ginCtx *gin.Context wrapper *GinWrapper uri any query any body any rawbody *[]byte form any header any timeout *time.Duration persistantData *preContextData // must be a ptr, so that we can get the values back in out Wrap func ignoreWrongContentType bool } type preContextData struct { sessionObj SessionObject } func (pctx *PreContext) URI(uri any) *PreContext { pctx.uri = uri return pctx } func (pctx *PreContext) Query(query any) *PreContext { pctx.query = query return pctx } func (pctx *PreContext) Body(body any) *PreContext { pctx.body = body return pctx } func (pctx *PreContext) RawBody(rawbody *[]byte) *PreContext { pctx.rawbody = rawbody return pctx } func (pctx *PreContext) Form(form any) *PreContext { pctx.form = form return pctx } func (pctx *PreContext) Header(header any) *PreContext { pctx.header = header return pctx } func (pctx *PreContext) WithTimeout(to time.Duration) *PreContext { pctx.timeout = &to return pctx } func (pctx *PreContext) WithSession(sessionObj SessionObject) *PreContext { pctx.persistantData.sessionObj = sessionObj return pctx } func (pctx *PreContext) IgnoreWrongContentType() *PreContext { pctx.ignoreWrongContentType = true return pctx } func (pctx PreContext) Start() (*AppContext, *gin.Context, *HTTPResponse) { if pctx.uri != nil { if err := pctx.ginCtx.ShouldBindUri(pctx.uri); err != nil { err = exerr.Wrap(err, "Failed to read uri"). WithType(exerr.TypeBindFailURI). Str("struct_type", fmt.Sprintf("%T", pctx.uri)). Build() return nil, nil, langext.Ptr(pctx.wrapper.buildRequestBindError(pctx.ginCtx, "URI", err)) } } if pctx.query != nil { if err := pctx.ginCtx.ShouldBindQuery(pctx.query); err != nil { err = exerr.Wrap(err, "Failed to read query"). WithType(exerr.TypeBindFailQuery). Str("struct_type", fmt.Sprintf("%T", pctx.query)). Build() return nil, nil, langext.Ptr(pctx.wrapper.buildRequestBindError(pctx.ginCtx, "QUERY", err)) } } if pctx.body != nil { if pctx.ginCtx.ContentType() == "application/json" { if brc, ok := pctx.body.(dataext.BufferedReadCloser); ok { // Ensures a fully reset (offset=0) buffer before parsing err := brc.Reset() if err != nil { err = exerr.Wrap(err, "Failed to read (brc.reset) json-body"). WithType(exerr.TypeBindFailJSON). Str("struct_type", fmt.Sprintf("%T", pctx.body)). Build() return nil, nil, langext.Ptr(pctx.wrapper.buildRequestBindError(pctx.ginCtx, "JSON", err)) } } if err := pctx.ginCtx.ShouldBindJSON(pctx.body); err != nil { err = exerr.Wrap(err, "Failed to read json-body"). WithType(exerr.TypeBindFailJSON). Str("struct_type", fmt.Sprintf("%T", pctx.body)). Build() return nil, nil, langext.Ptr(pctx.wrapper.buildRequestBindError(pctx.ginCtx, "JSON", err)) } } else { if !pctx.ignoreWrongContentType { err := exerr.New(exerr.TypeBindFailJSON, "missing JSON body"). Str("struct_type", fmt.Sprintf("%T", pctx.body)). Build() return nil, nil, langext.Ptr(pctx.wrapper.buildRequestBindError(pctx.ginCtx, "JSON", err)) } } } if pctx.rawbody != nil { if brc, ok := pctx.ginCtx.Request.Body.(dataext.BufferedReadCloser); ok { v, err := brc.BufferedAll() if err != nil { return nil, nil, langext.Ptr(pctx.wrapper.buildRequestBindError(pctx.ginCtx, "BODY", err)) } *pctx.rawbody = v } else { buf := &bytes.Buffer{} _, err := io.Copy(buf, pctx.ginCtx.Request.Body) if err != nil { return nil, nil, langext.Ptr(pctx.wrapper.buildRequestBindError(pctx.ginCtx, "BODY", err)) } *pctx.rawbody = buf.Bytes() } } if pctx.form != nil { if pctx.ginCtx.ContentType() == "multipart/form-data" { if err := pctx.ginCtx.ShouldBindWith(pctx.form, binding.Form); err != nil { err = exerr.Wrap(err, "Failed to read multipart-form"). WithType(exerr.TypeBindFailFormData). Str("struct_type", fmt.Sprintf("%T", pctx.form)). Build() return nil, nil, langext.Ptr(pctx.wrapper.buildRequestBindError(pctx.ginCtx, "FORM", err)) } } else if pctx.ginCtx.ContentType() == "application/x-www-form-urlencoded" { if err := pctx.ginCtx.ShouldBindWith(pctx.form, binding.Form); err != nil { err = exerr.Wrap(err, "Failed to read urlencoded-form"). WithType(exerr.TypeBindFailFormData). Str("struct_type", fmt.Sprintf("%T", pctx.form)). Build() return nil, nil, langext.Ptr(pctx.wrapper.buildRequestBindError(pctx.ginCtx, "FORM", err)) } } else { if !pctx.ignoreWrongContentType { err := exerr.New(exerr.TypeBindFailFormData, "missing form body"). Str("struct_type", fmt.Sprintf("%T", pctx.form)). Build() return nil, nil, langext.Ptr(pctx.wrapper.buildRequestBindError(pctx.ginCtx, "FORM", err)) } } } if pctx.header != nil { if err := pctx.ginCtx.ShouldBindHeader(pctx.header); 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(pctx.wrapper.buildRequestBindError(pctx.ginCtx, "HEADER", err)) } } ictx, cancel := context.WithTimeout(context.Background(), langext.Coalesce(pctx.timeout, pctx.wrapper.requestTimeout)) actx := CreateAppContext(pctx.ginCtx, ictx, cancel) if pctx.persistantData.sessionObj != nil { err := pctx.persistantData.sessionObj.Init(pctx.ginCtx, actx) if err != nil { actx.Cancel() return nil, nil, langext.Ptr(pctx.wrapper.buildRequestBindError(pctx.ginCtx, "INIT", err)) } } return actx, pctx.ginCtx, nil } func callPanicSafe(fn WHandlerFunc, pctx PreContext) (res HTTPResponse, stackTrace string, panicObj any) { defer func() { if rec := recover(); rec != nil { res = nil stackTrace = string(debug.Stack()) panicObj = rec } }() res = fn(pctx) return res, "", nil }