mirror of
https://github.com/go-gitea/gitea.git
synced 2026-08-13 23:41:16 +00:00
refactor: form binding validation (#38832)
Make "form-fetch-action" highlight the invalid field as "error"
This commit is contained in:
+49
-14
@@ -26,6 +26,8 @@ import (
|
||||
"gitea.dev/modules/web"
|
||||
"gitea.dev/modules/web/middleware"
|
||||
web_types "gitea.dev/modules/web/types"
|
||||
|
||||
"gitea.com/go-chi/binding"
|
||||
)
|
||||
|
||||
// Render represents a template render
|
||||
@@ -81,17 +83,22 @@ func GetWebContext(ctx context.Context) *Context {
|
||||
return webCtx
|
||||
}
|
||||
|
||||
// ValidateContext is a special context for form validation middleware. It may be different from other contexts.
|
||||
type ValidateContext struct {
|
||||
*Base
|
||||
}
|
||||
|
||||
// GetValidateContext gets a context for middleware form validation
|
||||
func GetValidateContext(req *http.Request) (ctx *ValidateContext) {
|
||||
func GetValidateContext(req *http.Request) (ctx *middleware.ValidateContext) {
|
||||
if ctxAPI, ok := req.Context().Value(apiContextKey).(*APIContext); ok {
|
||||
ctx = &ValidateContext{Base: ctxAPI.Base}
|
||||
ctx = &middleware.ValidateContext{
|
||||
Data: ctxAPI.Data,
|
||||
Locale: ctxAPI.Locale,
|
||||
Req: ctxAPI.Req,
|
||||
Resp: ctxAPI.Resp,
|
||||
}
|
||||
} else if ctxWeb, ok := req.Context().Value(WebContextKey).(*Context); ok {
|
||||
ctx = &ValidateContext{Base: ctxWeb.Base}
|
||||
ctx = &middleware.ValidateContext{
|
||||
Data: ctxWeb.Data,
|
||||
Locale: ctxWeb.Locale,
|
||||
Req: ctxWeb.Req,
|
||||
Resp: ctxWeb.Resp,
|
||||
}
|
||||
} else {
|
||||
panic("invalid context, expect either APIContext or Context")
|
||||
}
|
||||
@@ -254,15 +261,24 @@ func (ctx *Context) JSONOK() {
|
||||
ctx.JSON(http.StatusOK, map[string]any{"ok": true}) // this is only a dummy response, frontend seldom uses it
|
||||
}
|
||||
|
||||
func (ctx *Context) JSONError(msg any) {
|
||||
func buildJsonErrorMap(msg any) map[string]any {
|
||||
switch v := msg.(type) {
|
||||
case string:
|
||||
ctx.JSON(http.StatusBadRequest, map[string]any{"errorMessage": v, "renderFormat": "text"})
|
||||
return map[string]any{"errorMessage": v, "renderFormat": "text"}
|
||||
case template.HTML:
|
||||
ctx.JSON(http.StatusBadRequest, map[string]any{"errorMessage": v, "renderFormat": "html"})
|
||||
default:
|
||||
panic(fmt.Sprintf("unsupported type: %T", msg))
|
||||
return map[string]any{"errorMessage": v, "renderFormat": "html"}
|
||||
}
|
||||
panic(fmt.Sprintf("unsupported type: %T", msg))
|
||||
}
|
||||
|
||||
func (ctx *Context) JSONError(msg any) {
|
||||
ctx.JSON(http.StatusBadRequest, buildJsonErrorMap(msg))
|
||||
}
|
||||
|
||||
func (ctx *Context) JSONErrorWithField(msg any, field string) {
|
||||
m := buildJsonErrorMap(msg)
|
||||
m["errorFields"] = []string{field}
|
||||
ctx.JSON(http.StatusBadRequest, m)
|
||||
}
|
||||
|
||||
func (ctx *Context) JSONErrorNotFound(optMsg ...string) {
|
||||
@@ -270,5 +286,24 @@ func (ctx *Context) JSONErrorNotFound(optMsg ...string) {
|
||||
if msg == "" {
|
||||
msg = ctx.Locale.TrString("error.not_found")
|
||||
}
|
||||
ctx.JSON(http.StatusNotFound, map[string]any{"errorMessage": msg, "renderFormat": "text"})
|
||||
ctx.JSON(http.StatusNotFound, buildJsonErrorMap(msg))
|
||||
}
|
||||
|
||||
func GetFetchActionForm[T interface {
|
||||
*E
|
||||
middleware.Form
|
||||
}, E any](ctx *Context) *E {
|
||||
if web.IsFormSet(ctx) {
|
||||
panic("don't mix fetch-action form validation with template-based form validation")
|
||||
}
|
||||
middleware.SkipTmplFormValidationError(ctx)
|
||||
form := T(new(E))
|
||||
errs := binding.Bind(ctx.Req, form)
|
||||
errorMessage, fieldName, _ := middleware.BuildValidationErrorForUser(form, ctx.Locale, errs)
|
||||
if errorMessage != "" {
|
||||
ctx.Resp.Header().Set("Content-Type", "application/json")
|
||||
ctx.JSONErrorWithField(errorMessage, fieldName)
|
||||
return nil
|
||||
}
|
||||
return form
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user