Validating query string parameters
Thanks to the readInt() helper that we made in the previous chapter, our API should already be returning validation errors if the page and page_size query string parameters don’t contain integer values. Go ahead and try it out if you like:
$ curl "localhost:4000/v1/movies?page=abc&page_size=abc"
{
"error": {
"page": "must be an integer value",
"page_size": "must be an integer value"
}
}
But we still need to perform some additional sanity checks on the query string values provided by the client. In particular, we want to check that:
- The
pagevalue is between 1 and 10,000,000. - The
page_sizevalue is between 1 and 100. - The
sortparameter contains a known and supported value for our movies table. Specifically, we’ll allow"id","title","year","runtime","-id","-title","-year"or"-runtime".
To fix this, let’s open up the internal/data/filters.go file and create a new ValidateFilters() function which conducts these checks on the values.
We’ll follow the same pattern that we used for the ValidateMovie() function earlier on to do this, like so:
package data import ( "greenlight.alexedwards.net/internal/validator" // New import ) // Add a SortSafelist field to hold the supported sort values. type Filters struct { Page int PageSize int Sort string SortSafelist []string } func ValidateFilters(v *validator.Validator, f Filters) { // Check that the page and page_size parameters contain sensible values. v.Check(f.Page > 0, "page", "must be greater than zero") v.Check(f.Page <= 10_000_000, "page", "must be a maximum of 10 million") v.Check(f.PageSize > 0, "page_size", "must be greater than zero") v.Check(f.PageSize <= 100, "page_size", "must be a maximum of 100") // Check that the sort parameter matches a value in the safelist. v.Check(validator.PermittedValue(f.Sort, f.SortSafelist...), "sort", "invalid sort value") }
Then we need to update our listMoviesHandler to set the supported values in the SortSafelist field, and subsequently call this new ValidateFilters() function.
package main ... func (app *application) listMoviesHandler(w http.ResponseWriter, r *http.Request) { var input struct { Title string Genres []string data.Filters } v := validator.New() qs := r.URL.Query() input.Title = app.readString(qs, "title", "") input.Genres = app.readCSV(qs, "genres", []string{}) input.Filters.Page = app.readInt(qs, "page", 1, v) input.Filters.PageSize = app.readInt(qs, "page_size", 20, v) input.Filters.Sort = app.readString(qs, "sort", "id") // Add the supported sort values for this endpoint to the sort safelist. input.Filters.SortSafelist = []string{"id", "title", "year", "runtime", "-id", "-title", "-year", "-runtime"} // Execute the validation checks on the Filters struct and send a response // containing the errors if necessary. if data.ValidateFilters(v, input.Filters); !v.Valid() { app.failedValidationResponse(w, r, v.Errors) return } fmt.Fprintf(w, "%+v\n", input) }
If you restart the API and try making a request with some invalid page, page_size and sort parameters, you should now receive an error response containing the relevant validation failure messages. Similar to this:
$ curl "localhost:4000/v1/movies?page=-1&page_size=-1&sort=foo"
{
"error": {
"page": "must be greater than zero",
"page_size": "must be greater than zero",
"sort": "invalid sort value"
}
}
Feel free to test this out more if you like, and try sending different query string values until you’re confident that the validation checks are all working correctly.