Automatic form decoding

main
Drew Bednar 2 months ago
parent a75659f653
commit 34e80ddda2

@ -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:"-"`
}

@ -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

@ -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())

@ -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)

Loading…
Cancel
Save