Let's Go Further Filtering, sorting, and pagination › Validating query string parameters
Previous · Contents · Next
Chapter 9.2.

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:

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:

File: internal/data/filters.go
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.

File: cmd/api/movies.go
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.