Reputation: 1685
I would like to make a reusable middleware for validation throughout my API. Here, validation is done through govalidator, so I just need to pass the validation rules and a DTO where the request is mapped into.
func ValidationMiddleware(next http.HandlerFunc, validationRules govalidator.MapData, dto interface{}) http.HandlerFunc {
return func(rw http.ResponseWriter, r *http.Request) {
utils.SetResponseHeaders(rw)
opts := govalidator.Options{
Request: r,
Data: &dto,
Rules: validationRules,
RequiredDefault: true,
}
v := govalidator.New(opts)
err := v.ValidateJSON()
if err != nil {
fmt.Println("Middleware found an error")
err := utils.ErrorWrapper{
StatusCode: 400,
Type: "Bad Request",
Message: err,
}
err.ThrowError(rw)
return
}
next(rw, r)
}
}
This is how the HandleFunc
looks like:
var rules govalidator.MapData = govalidator.MapData{
"name": []string{"required"},
"description": []string{"required"},
"price": []string{"required", "float"},
}
func RegisterItemsRouter(router *mux.Router) {
itemsRouter := router.PathPrefix("/inventory").Subrouter()
itemsRouter.HandleFunc("/", middleware.ValidationMiddleware(addItem, rules, dto.CreateItem{
Name: "",
Description: "",
Price: govalidator.Float64{},
})).Methods("POST")
}
If no errors are found, govalidator parses the request body into the dto struct, so I would like to pass this down into the next handler and avoid trying to parse the body a second time.
How can I pass this struct down to the next HandleFunc?
Upvotes: 1
Views: 1089
Reputation: 51652
From the code, it appears like you pass the request to the validator options, and the validator reads and validates the body from that. This poses several problems: An HTTP request can only be read once, so either the validator somehow returns you that unmarshaled object, or you have to read the body before validating it.
Going for the second solution, the first thing is your validator has to know the type of the object it has to unmarshal to:
func ValidationMiddleware(next http.HandlerFunc, factory func() interface{}, validationRules govalidator.MapData, dto interface{}) http.HandlerFunc {
return func(rw http.ResponseWriter, r *http.Request) {
newInstance:=factory()
data, err:=ioutil.ReadAll(r.Body)
json.Unmarshal(data,newInstance)
// Validate newInstance here
r.WithContext(context.WithValue(r.Context(),"data",newInstance))
next(wr,r)
}
}
Where the func factory
is a function that creates an instance of the object that will be unmarshaled for a request:
func RegisterItemsRouter(router *mux.Router) {
itemsRouter := router.PathPrefix("/inventory").Subrouter()
itemsRouter.HandleFunc("/", middleware.ValidationMiddleware(addItem, func() interface{} { return &TheStruct{}}, rules, dto.CreateItem{
Name: "",
Description: "",
Price: govalidator.Float64{},
})).Methods("POST")
}
This way, when a new request comes, a new instance of TheStruct
will be created and unmarshaled, then validated. If the validation is ok, it will be placed into the context, so the next middleware or the handler can get it:
func handler(wr http.ResponseWriter,r *http.Request) {
item:=r.Context().Value("data").(*TheStruct)
...
}
Upvotes: 1