Validation Snippets for Go

Over the past few years I've built up a collection of snippets for validating inputs in Go. There's nothing new or groundbreaking here, but hopefully they might save you some time.

The snippets assume that the data to validate is stored as strings in r.Form, but the principles are the same no matter where the data has come from.

  1. Required inputs
  2. Blank text
  3. Min and max length (bytes)
  4. Min and max length (number of characters)
  5. Starts with, ends with and contains
  6. Matches regular expression pattern
  7. Unicode character range
  8. Email validation
  9. URL validation
  10. Integers
  11. Floats
  12. Date
  13. Datetime-local
  14. Radio, Select and Datalist (one-in-set)
  15. Checkboxes (many-in-set)
  16. Single checkbox

Required inputs

If you have the HTML form:

<input type="text" name="foo">

You can verify that a value for the "foo" field has been submitted with:

if r.Form.Get("foo") == "" {
    fmt.Println("error: foo is required")
}

For checkbox and select inputs this will ensure that at least one item has been checked.

Blank text

If you have the HTML form:

<input type="text" name="foo">

You can verify that a value for the "foo" field isn't blank (i.e. contains whitespace only) with the strings.TrimSpace function:

import "strings"
···
if strings.TrimSpace(r.Form.Get("foo")) == "" {
    fmt.Println("error: foo must not be blank")
}

Min and max length (bytes)

If you have the HTML form:

<input type="text" name="foo">

You can verify that the "foo" field contains a certain number of bytes with the builtin len function:

l := len(r.Form.Get("foo"))
if l < 5 || l > 10 {
    fmt.Println("error: foo must be between 5 and 10 bytes long")
}

Min and max length (number of characters)

If you have the HTML form:

<input type="text" name="foo">

You can verify that the "foo" field contains a certain number of characters with the utf8.RuneCountInString function. This is subtly different to checking the number of bytes. For example, the string "Zoë" contains 3 characters but is 4 bytes long because of the accented character.

import "unicode/utf8"
···
l := utf8.RuneCountInString(r.Form.Get("foo"))
if l < 5 || l > 10 {
    fmt.Println("error: foo must be between 5 and 10 characters long")
}

Starts with, ends with and contains

If you have the HTML form:

<input type="text" name="foo">

You can verify that the "foo" field starts with, ends with, or contains a particular string using the functions in the strings package:

import "strings"
···
// Check that the field value starts with 'abc'.
if !strings.HasPrefix(r.Form.Get("foo"), "abc") {
    fmt.Println("error: foo does not start with 'abc'")
}

// Check that the field value ends with 'abc'.
if !strings.HasSuffix(r.Form.Get("foo"), "abc") {
    fmt.Println("error: foo does not end with 'abc'")
}

// Check that the field value contains 'abc' anywhere in it.
if !strings.Contains(r.Form.Get("foo"), "abc") {
    fmt.Println("error: foo does not contain 'abc'")
}

Matches regular expression pattern

If you have the HTML form:

<input type="text" name="foo">

You can verify that the "foo" field matches a particular regular expression using the regexp package. For example, to check that it matches the pattern ^[a-z]{4}\.[0-9]{2}$ (four lowercase letters followed by a period and two digits):

import "regexp"
···
// Pre-compiling the regular expression and storing it in a variable is more efficient
// if you're going to use it multiple times. The regexp.MustCompile function will
// panic on failure.
var rxPat = regexp.MustCompile(`^[a-z]{4}.[0-9]{2}$`)

if !rxPat.MatchString(r.Form.Get("foo")) {
    fmt.Println("error: foo does not match the required pattern")
}

Note that because the dot character has a special meaning in regular expressions, we escaped it using the \ character so it is interpreted as a literal period character instead. In the example above we also used a raw string for the regular expression. If you use an interpreted string (i.e. a string surrounded by double quotes), you need to escape the backslash too because that's the escape character for interpreted strings. So you would need to write:

var rxPat = regexp.MustCompile("^[a-z]{4}\.[0-9]{2}$")

If you're not familiar with regular expressions then this guide from Mozilla is a good explanation.

Unicode character range

If you have the HTML form:

<input type="text" name="foo">

You can verify that the "foo" field only contains characters in a certain unicode range using the regexp package. For example, to check that it contains only Cyrillic characters in the two unicode blocks 0400 - 04FF (Cyrillic) and 0500 - 052F (Cyrillic Supplementary):

import "regexp"
···
// Use an interpreted string and the \u escape notation to create a regular
// expression matching the range of characters in the two unicode code blocks.
var rxCyrillic = regexp.MustCompile("^[\u0400-\u04FF\u0500-\u052F]+$")

if !rxCyrillic.MatchString(r.Form.Get("foo")) {
    fmt.Println("error: foo must only contain Cyrillic characters")
}

Email validation

If you have the HTML form:

<input type="email" name="foo">

You can sanity check that the "foo" field contains an email address using the regexp package. Choosing a regular expression to use for email validation is a contentious topic, but as a starting point I would suggest the pattern recommended by the W3C and Web Hypertext Application Technology Working Group.

In addition, the email addresses have a practical limit of 254 bytes. Putting those together, a decent sanity check is:

import "regexp"
···
var rxEmail = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$")

e := r.Form.Get("foo")
if len(e) > 254 || !rxEmail.MatchString(e) {
    fmt.Println("error: foo is not a valid email address")
}

Note that we have to use an interpreted string for the regular expression because it contains a backtick character (which means we can't use a raw string).

URL validation

If you have the HTML form:

<input type="url" name="foo">

You can verify that the "foo" field contains a valid URL by first parsing it with the url.Parse function. This will take a URL string, break it into it's component pieces, and store it as a url.URL struct. You can then sanity check the component pieces as necessary.

For instance, to check that a URL is absolute (i.e has both a scheme and host) and that the scheme is either http or https:

import "net/url"
···
// If there are any major problems with the format of the URL, url.Parse() will
// return an error.
u, err := url.Parse(r.Form.Get("foo"))
if err != nil {
    fmt.Println("error: foo is not a valid URL")
} else if u.Scheme == "" || u.Host == "" {
    fmt.Println("error: foo must be an absolute URL")
} else if u.Scheme != "http" && u.Scheme != "https" {
    fmt.Println("error: foo must begin with http or https")
}

Integers

If you have the HTML form:

<input type="number" name="foo" min="0" max="100" step="5">
You can verify that the "foo" field contains an integer by parsing it with the strconv.Atoi function. You can then sanity check the integer as necessary.

For instance, to check that an integer is a multiple of 5 between 0 and 100:

import "strconv"
···
n, err := strconv.Atoi(r.Form.Get("foo"))
if err != nil {
    fmt.Println("error: foo must be an integer")
} else if n < 0 || n > 10  {
    fmt.Printf("error: foo must be between 0 and 100")
} else if  n%5 != 0 {
    fmt.Println("error: foo must be an multiple of 5")
}

Floats

If you have the HTML form:

<input type="number" name="foo" min="0" max="1" step="0.01">

You can verify that the "foo" field contains an integer by parsing it with the strconv.ParseFloat function. You can then sanity check the float as necessary.

For instance, to check that an float is a between 0 and 1:

import "strconv"

n, err := strconv.ParseFloat(r.Form.Get("foo"), 64)
if err != nil {
    fmt.Println("error: foo must be a float")
} else if n < 0 || n > 1  {
    fmt.Printf("error: foo must be between 0 and 1")
}

Date

If you have the HTML form:

<input type="date" name="foo" min="2017-01-01" max="2017-12-31">

You can verify that the "foo" field contains a valid date by parsing it with the time.Parse function. It will return an error if the date is not real: any day of month larger than 31 is rejected, as is February 29 in non-leap years, February 30, February 31, April 31, June 31, September 31, and November 31.

If you're not familiar with time.Parse, it converts strings with a given format into a time.Time object. You specify what the format is by passing the reference time (Mon Jan 2 15:04:05 -0700 MST 2006) as the first parameter, laid-out in the format you want. If you are expecting a date in the format YYYY-MM-DD then the reference time is 2006-01-02.

You can then use the various functions in the time package to sanity check the date as necessary.

For instance, to check that an date is valid and between 2017-01-01 and 2017-12-31:

import "time"
···
d, err := time.Parse("2006-01-02", r.Form.Get("foo"))
if err != nil {
    fmt.Printf("error: foo is not a valid date")
} else if d.Year() != 2017 {
    fmt.Printf("error: foo is not between 2017-01-01 and 2017-12-31")
}

Datetime-local

If you have the HTML form:

<input type="datetime-local" name="foo" min="2017-01-01" max="2017-12-31">

You can verify that the "foo" field contains a valid datetime for a specific location by parsing it with the time.ParseInLocation function. This will return an error if the date is not real: any day of month larger than 31 is rejected, as is February 29 in non-leap years, February 30, February 31, April 31, June 31, September 31, and November 31.

For instance, to check that an datetime for the "Europe/Vienna" timezone is valid and between 2017-01-01 00:00 and 2017-12-31 23:59:

import "time"
···

// Load the users local time zone. This accepts a location name corresponding
// to a file in your IANA Time Zone database.
loc, err := time.LoadLocation("Europe/Vienna")
if err != nil {
    ···
}

d, err := time.ParseInLocation("2006-01-02T15:04:05", r.Form.Get("foo"), loc)
if err != nil {
    fmt.Printf("error: foo is not a valid datetime")
} else if d.Year() != 2017 {
    fmt.Printf("error: foo is not between 2017-01-01 00:00:00 and 2017-12-31 23:59:00")
}

The time zone database needed by LoadLocation may not be present on all systems, especially non-Unix systems. LoadLocation looks in the directory or uncompressed zip file named by the ZONEINFO environment variable, if any, then looks in known installation locations on Unix systems, and finally looks in $GOROOT/lib/time/zoneinfo.zip.

Radio, Select and Datalist (one-in-a-set) validation

If you have the HTML form:

<input type="radio" name="foo" value="wibble"> Wibble
<input type="radio" name="foo" value="wobble"> Wobble
<input type="radio" name="foo" value="wubble"> Wubble

You can check that the submitted value for the "foo" field is one of a known set like this:

set := map[string]bool{"wibble": true, "wobble": true, "wubble": true}

if !set[r.Form.Get("foo")] {
    fmt.Printf("error: foo not match 'wibble', 'wobble' or 'wubble'")
}

Checkboxes (many-in-a-set) validation

If you have the HTML form:

<input type="checkbox" name="foo" value="wibble"> Wibble
<input type="checkbox" name="foo" value="wobble"> Wobble
<input type="checkbox" name="foo" value="wubble"> Wubble

To validate these, and make sure that all values sent by the form are either wibble, wobble or wubble, we need to access the underlying form data directly and range over each value:

You can check that the submitted values for the "foo" field are part of a known set like this:

set := map[string]bool{"wibble": true, "wobble": true, "wubble": true}

for _, f := range r.Form["foo"] {
    if !set[f] {
        fmt.Printf("error: foo does not match 'wibble', 'wobble' or 'wubble'")
        break
    }
}

Single checkboxes

Sometimes you might have a single checkbox, and you want to verify that it has been checked. A common example is an "I accept the terms" checkbox on a form.

If you have the HTML form:

<input type="checkbox" name="foo" value="checked"> I accept the terms

You can verify that it has been checked like this:

if r.Form.Get("foo") != "checked" {
    fmt.Println("foo must be checked")
}