diff --git a/framework/controller/controller.gotext b/framework/controller/controller.gotext index bbab6348..d6d662a8 100644 --- a/framework/controller/controller.gotext +++ b/framework/controller/controller.gotext @@ -69,6 +69,19 @@ func ({{$action.Short}} *{{ $.Pascal }}{{$action.Pascal}}Action) handler(httpRes JSON: response.Status(400).Set("Content-Type", "application/json").JSON(map[string]string{"error": err.Error()}), } } + // validate input + {{- if $action.HasCustomValidation }} + // single input struct has validate method + if err := in.Valid(); err != nil { + return &response.Format{ + {{- if ne $action.Method "GET" }} + HTML: response.Status(http.StatusSeeOther).RedirectBack(httpRequest.URL.Path), + {{- end }} + JSON: response.Status(400).Set("Content-Type", "application/json").JSON(map[string]string{"error": err.Error()}), + } + } + {{- end }} + {{- end }} {{- with $provider := $action.Provider }} controller, err := {{ $provider.Name }}( diff --git a/framework/controller/controller_test.go b/framework/controller/controller_test.go index 37f4a84e..f2a434a5 100644 --- a/framework/controller/controller_test.go +++ b/framework/controller/controller_test.go @@ -2792,3 +2792,36 @@ func TestIndexControllerWithRootIndexAction(t *testing.T) { `)) is.NoErr(app.Close()) } + +// Asserts the Validate() method is called on single input +// structs. +func TestInputValidateMethod(t *testing.T) { + is := is.New(t) + ctx := context.Background() + dir := t.TempDir() + td := testdir.New(dir) + // root controller with input struct to validate + td.Files["controller/controller.go"] = ` + package controller + import "errors" + type Controller struct{} + type Input struct{} + func (in *Input) Valid() error { + return errors.New("") + } + func (c *Controller) Index(in *Input) []string { + return []string{} + } + ` + is.NoErr(td.Write(ctx)) + cli := testcli.New(dir) + app, err := cli.Start(ctx, "run") + is.NoErr(err) + defer app.Close() + res, err := app.GetJSON("/") + is.NoErr(err) + is.NoErr(res.DiffHeaders(` + HTTP/1.1 400 Bad Request + Content-Type: application/json + `)) +} diff --git a/framework/controller/loader.go b/framework/controller/loader.go index f936540e..d64a5999 100644 --- a/framework/controller/loader.go +++ b/framework/controller/loader.go @@ -300,6 +300,13 @@ func (l *loader) loadActionParam(param *parser.Param, nth, numParams int) *Actio switch { // Single struct input case numParams == 1 && dec.Kind() == parser.KindStruct: + // this should always work because kind is KindStruct + stct := dec.Package().Struct(dec.Name()) + validateMethod := stct.Method("Valid") + if validateMethod != nil && len(validateMethod.Results()) == 1 && validateMethod.Results()[0].IsError() { + // mark that the action param has Valid() error + ap.HasValidMethod = true + } ap.Variable = "in" // Handle context.Context case ap.IsContext(): @@ -342,6 +349,7 @@ func (l *loader) loadType(dt parser.Type, dec parser.Declaration) string { func (l *loader) loadActionInput(params []*ActionParam) string { if len(params) == 1 && params[0].Kind == string(parser.KindStruct) { + // single struct input return params[0].Type } return l.loadActionInputStruct(params) diff --git a/framework/controller/state.go b/framework/controller/state.go index 47cf284b..2b620679 100644 --- a/framework/controller/state.go +++ b/framework/controller/state.go @@ -60,6 +60,18 @@ type Action struct { PropsKey string } +func (a *Action) HasSingleInput() bool { + return len(a.Params) == 1 +} + +func (a *Action) HasCustomValidation() bool { + if a.HasSingleInput() && a.Params[0].HasValidMethod { + return true + } else { + return false + } +} + // View struct type View struct { Route string @@ -67,13 +79,14 @@ type View struct { // ActionParam struct type ActionParam struct { - Name string - Pascal string - Snake string - Type string - Kind string - Variable string - Tag string + Name string + Pascal string + Snake string + Type string + Kind string + Variable string + Tag string + HasValidMethod bool } func (ap *ActionParam) IsContext() bool {