diff --git a/internal/server/form.go b/internal/server/form.go index 716b654..4602c4b 100644 --- a/internal/server/form.go +++ b/internal/server/form.go @@ -12,9 +12,16 @@ import "git.runcible.io/learning/ratchet/internal/validator" // Remove the explicit FieldErrors struct field and instead embed the Validator // struct. Embedding this means that our snippetCreateForm "inherits" all the // fields and methods of our Validator struct (including the FieldErrors field). +// +// MOVING TO go-playground/form +// Update our snippetCreateForm struct to include struct tags which tell the +// decoder how to map HTML form values into the different struct fields. So, for +// example, here we're telling the decoder to store the value from the HTML form +// input with the name "title" in the Title field. The struct tag `form:"-"` +// tells the decoder to completely ignore a field during decoding. type snippetCreateForm struct { - Title string - Content string - Expires int - validator.Validator + Title string `form:"title"` + Content string `form:"content"` + Expires int `form:"expires"` + validator.Validator `form:"-"` } diff --git a/internal/server/handlers.go b/internal/server/handlers.go index acca6f9..4cd6e13 100644 --- a/internal/server/handlers.go +++ b/internal/server/handlers.go @@ -10,6 +10,7 @@ import ( "git.runcible.io/learning/ratchet/internal/model" "git.runcible.io/learning/ratchet/internal/validator" + "github.com/go-playground/form/v4" ) // TODO function should accept and a pointer to an interface allowing for mocking in tests. @@ -139,7 +140,7 @@ func handleSnippetCreateGet(tc *TemplateCache) http.Handler { // snippetCreate handles display of the form used to create snippets // // curl -iL -d "" http://localhost:5001/snippet/create -func handleSnippetCreatePost(logger *slog.Logger, tc *TemplateCache, snippetService *model.SnippetService) http.Handler { +func handleSnippetCreatePost(logger *slog.Logger, tc *TemplateCache, formDecoder *form.Decoder, snippetService *model.SnippetService) http.Handler { return http.HandlerFunc( func(w http.ResponseWriter, r *http.Request) { // example of a custom header. Must be done before calling WriteHeader @@ -173,24 +174,36 @@ func handleSnippetCreatePost(logger *slog.Logger, tc *TemplateCache, snippetServ return } + var form snippetCreateForm + + // AUTOMATIC FORM PROCESSING + + err = formDecoder.Decode(&form, r.PostForm) + + if err != nil { + clientError(w, http.StatusBadRequest) + return + } + // OLD WAY // The r.PostForm.Get() method always returns the form data as a *string*. // However, we're expecting our expires value to be a number, and want to // represent it in our Go code as an integer. So we need to manually convert // the form data to an integer using strconv.Atoi(), and we send a 400 Bad // Request response if the conversion fails. - expires, err := strconv.Atoi(r.PostForm.Get("expires")) - if err != nil { - clientError(w, http.StatusBadRequest) - return - } + // expires, err := strconv.Atoi(r.PostForm.Get("expires")) + // if err != nil { + // clientError(w, http.StatusBadRequest) + // return + + // } // Create an instance of the snippetCreateForm struct containing the values // from the form and an empty map for any validation errors. - form := snippetCreateForm{ - Title: r.PostForm.Get("title"), - Content: r.PostForm.Get("content"), - Expires: expires, - } + // form := snippetCreateForm{ + // Title: r.PostForm.Get("title"), + // Content: r.PostForm.Get("content"), + // Expires: expires, + // } // VALIDATION // THE OLD WAY diff --git a/internal/server/routes.go b/internal/server/routes.go index 7d3cb1f..8d6dcda 100644 --- a/internal/server/routes.go +++ b/internal/server/routes.go @@ -6,12 +6,14 @@ import ( "net/http" "git.runcible.io/learning/ratchet/internal/model" + "github.com/go-playground/form/v4" ) func addRoutes(mux *http.ServeMux, logger *slog.Logger, tc *TemplateCache, db *sql.DB, + fd *form.Decoder, snippetService *model.SnippetService) http.Handler { // /{$} is used to prevent subtree path patterns from acting like a wildcard @@ -21,7 +23,7 @@ func addRoutes(mux *http.ServeMux, mux.Handle("GET /{$}", handleHome(logger, tc, snippetService)) mux.Handle("GET /snippet/view/{id}", handleSnippetView(logger, tc, snippetService)) mux.Handle("GET /snippet/create", handleSnippetCreateGet(tc)) - mux.Handle("POST /snippet/create", handleSnippetCreatePost(logger, tc, snippetService)) + mux.Handle("POST /snippet/create", handleSnippetCreatePost(logger, tc, fd, snippetService)) // mux.Handle("/something", handleSomething(logger, config)) // mux.Handle("/healthz", handleHealthzPlease(logger)) // mux.Handle("/", http.NotFoundHandler()) diff --git a/internal/server/server.go b/internal/server/server.go index f8b3291..a520dde 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -6,6 +6,7 @@ import ( "net/http" "git.runcible.io/learning/ratchet/internal/model" + "github.com/go-playground/form/v4" ) type RatchetServer struct { @@ -16,13 +17,14 @@ type RatchetServer struct { //Services used by HTTP routes snippetService *model.SnippetService UserService model.UserService + formDecoder *form.Decoder } func NewRatchetServer(logger *slog.Logger, tc *TemplateCache, db *sql.DB) *RatchetServer { rs := new(RatchetServer) rs.logger = logger rs.snippetService = &model.SnippetService{DB: db} - + rs.formDecoder = form.NewDecoder() rs.templateCache = tc // TODO implement middleware that disables directory listings fileServer := http.FileServer(http.Dir("./ui/static/")) @@ -33,7 +35,7 @@ func NewRatchetServer(logger *slog.Logger, tc *TemplateCache, db *sql.DB) *Ratch // Mux Router implements the Handler interface. AKA it has a ServeHTTP receiver. // SEE we can really clean things up by moving this into routes.go and handlers.go - wrappedMux := addRoutes(router, rs.logger, rs.templateCache, db, rs.snippetService) + wrappedMux := addRoutes(router, rs.logger, rs.templateCache, db, rs.formDecoder, rs.snippetService) rs.Handler = CommonHeaderMiddleware(wrappedMux) rs.Handler = RequestLoggingMiddleware(rs.Handler, logger) rs.Handler = RecoveryMiddleware(rs.Handler)