Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom Validation #259

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions framework/controller/controller.gotext
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The big question is going to be what happens with the error message? Right now if a form fails, there's no feedback sent back to the user.

Flash messages are typically the solution to this where you store a value in a cookie that gets removed after it's pulled out. I think the ultimate solution is to have the ability to add custom flash messages via the session, but if there's any temporary solution to get this working where you set the cookie, pull it out and pass to the view, I'd be open to pursuing that. Here's what I've thought about so far with sessions: #56.

Otherwise maybe we only enable this for JSON? Does your use case fit with that limitation?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, I think flash messages should be the behavior here. I'm thinking to add some other stuff to this PR but then put in on pause until we have sessions and something like flash messages.

Copy link
Contributor

@matthewmueller matthewmueller Aug 27, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool, sounds good! I'm going to get back to feature development very soon. Just trying to wrap up generator caching to allow for running more expensive generators (e.g. generators that call go run), so hopefully we'll get sessions soon!

{{- 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 }}(
Expand Down
33 changes: 33 additions & 0 deletions framework/controller/controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
`))
}
8 changes: 8 additions & 0 deletions framework/controller/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand Down Expand Up @@ -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)
Expand Down
27 changes: 20 additions & 7 deletions framework/controller/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,20 +60,33 @@ 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
}

// 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 {
Expand Down