Skip to content

Instantly share code, notes, and snippets.

@xeoncross
Last active May 17, 2019 03:43
Show Gist options
  • Save xeoncross/9e1869bf17a4e3b386417cd362a7f8fa to your computer and use it in GitHub Desktop.
Save xeoncross/9e1869bf17a4e3b386417cd362a7f8fa to your computer and use it in GitHub Desktop.
Simple validation middleware following after the idea of gongular but without doing things at the handler level.
package validation_middleware
import (
"bytes"
"encoding/json"
"fmt"
"log"
"net/http"
"net/http/httptest"
"reflect"
"testing"
"github.com/asaskevich/govalidator"
)
// What if, instead of setting the handler as the struct we duplicate/populate
// we create a middleware that passes the correct object if everything succeds?
// This would mean you could still create http.Handler's as you see fit, but
// passing this middleware would seem simpler..?
//
// router.GET('/', ValidateMiddleware(handlers.NewPost, &NewPost{}))
// InputNewPost defines the POST params we want
type InputNewPost struct {
Title string `valid:"alphanum,required"`
Email string `valid:"email,required"`
Message string `valid:"ascii,required"`
Date string `valid:"-"`
}
// Alternative: https://stackoverflow.com/a/29169727/99923
// func clear(v interface{}) {
// p := reflect.ValueOf(v).Elem()
// p.Set(reflect.Zero(p.Type()))
// }
type MyHandler func(w http.ResponseWriter, r *http.Request, i interface{})
// ValidateMiddleware requires object be a struct pointer!
// v1
// func ValidateMiddleware(h http.Handler, object interface{}) http.Handler {
// v2
func ValidateMiddleware(h MyHandler, object interface{}) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Clone struct (avoids race conditions)
objectElem := reflect.TypeOf(object).Elem()
o := reflect.New(objectElem).Elem()
var err error
err = json.NewDecoder(r.Body).Decode(o.Addr().Interface())
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
var isValid bool
isValid, err = govalidator.ValidateStruct(o.Addr().Interface())
// if err != nil {
// http.Error(w, err.Error(), http.StatusInternalServerError)
// return
// }
if !isValid {
validation := govalidator.ErrorsByField(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
fmt.Println(validation)
return
}
// v1
// h.ServeHTTP(w, r)
// v2
h(w, r, o.Addr().Interface())
})
}
func TestMiddleware(t *testing.T) {
successResponse := "SUCCESS"
data := InputNewPost{
Title: "FooBar",
Email: "email@example.com",
Message: "Hello there",
Date: "yes",
}
b, err := json.Marshal(data)
if err != nil {
log.Fatal(err)
}
req, err := http.NewRequest("POST", "/", bytes.NewReader(b))
if err != nil {
t.Fatal(err)
}
req.Header.Add("Content-Type", "application/json")
rr := httptest.NewRecorder()
// v1 plain HTTP handler wrapping
// h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// w.Write([]byte(successResponse))
// })
// v2, custom function
h := func(w http.ResponseWriter, r *http.Request, i interface{}) {
in := i.(*InputNewPost)
// if in, ok := i.(*InputNewPost); !ok {
// http.Error(w, "What???", http.StatusInternalServerError)
// return
// }
e := json.NewEncoder(w)
e.SetIndent("", " ")
err = e.Encode(in)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
// w.Write([]byte(successResponse))
}
router := http.NewServeMux()
router.Handle("/", ValidateMiddleware(h, &InputNewPost{}))
// TODO
// router := httprouter.New()
// router.POST("/", ValidateMiddleware(h, &InputNewPost{}))
router.ServeHTTP(rr, req)
got := rr.Body.String()
if status := rr.Code; status != http.StatusOK {
t.Errorf("handler returned wrong status code: got %v want %v", status, http.StatusOK)
t.Error(rr.Body.String())
}
if got != successResponse {
t.Errorf("handler returned wrong body:\n\tgot: %s\n\twant: %s", got, successResponse)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment