Riyaz Khan
Riyaz Khan

Reputation: 3278

How to validate an email address in Golang

I have checked StackOverflow and couldn't find any question that answers "how do I validate an email address in Go?".

After some research, I solved it to fill my need.

I have this regex and function in Go, which work fine:

import (
    "fmt"
    "regexp"
)

func main() {
    fmt.Println(isEmailValid("[email protected]")) // true
    fmt.Println(isEmailValid("[email protected]")) // true -- expected "false" 
}


// isEmailValid checks if the email provided is valid by regex.
func isEmailValid(e string) bool {
    emailRegex := 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])?)*$")
    return emailRegex.MatchString(e)
}

The problem is that it allows special characters that I don't want. I tried to use some from other languages' regex expression, but it throws the error "unknown escape" in debug.

Could anyone correct my regex or suggest another Go module?

Upvotes: 59

Views: 65318

Answers (6)

Slavik Meltser
Slavik Meltser

Reputation: 10401

You can create a function IsEmail which is based on the standard library mail

func IsEmail(email string) bool {
  emailAddress, err := mail.ParseAddress(email)
  return err == nil && emailAddress.Address == email
}

Test cases:

func main() {
  fmt.Println("'[email protected]'", IsEmail("[email protected]")) // true
  fmt.Println("'asd <[email protected]>'", IsEmail("asd <[email protected]>")) // false
  fmt.Println("'a@b'", IsEmail("a@b")) // true
}

Upvotes: 4

kouhadi
kouhadi

Reputation: 557

This external package govalidator is doing the job perfectly for me.

import (
  "fmt"
  "github.com/asaskevich/govalidator"
)
func main() {
    d := govalidator.IsEmail("helloworld@") 
    fmt.Println(d) // false
}

Upvotes: 1

icza
icza

Reputation: 418585

The standard lib has email parsing and validation built in, simply use: mail.ParseAddress().

A simple "is-valid" test:

func valid(email string) bool {
    _, err := mail.ParseAddress(email)
    return err == nil
}

Testing it:

for _, email := range []string{
    "[email protected]",
    "bad-example",
} {
    fmt.Printf("%18s valid: %t\n", email, valid(email))
}

Which outputs (try it on the Go Playground):

  [email protected] valid: true
       bad-example valid: false

NOTE:

The net/mail package implements and follows the RFC 5322 specification (and extension by RFC 6532). This means a seemingly bad email address like bad-example@t is accepted and parsed by the package because it's valid according to the spec. t may be a valid local domain name, it does not necessarily have to be a public domain. net/mail does not check if the domain part of the address is a public domain, nor that it is an existing, reachable public domain.

Upvotes: 139

peti
peti

Reputation: 846

Since I find regexp hard to read I prefer readable boring code. For example:

// Accepts at least the [email protected] pattern.
func isEmailAddress(v string) bool {
    if v == "" {
        return false
    }
    if containsWhitespace(v) {
        return false
    }

    iAt := strings.IndexByte(v, '@')
    if iAt == -1 {
        return false
    }

    localPart := v[:iAt]
    if localPart == "" {
        return false
    }

    domain := v[iAt+1:]
    if domain == "" {
        return false
    }

    iDot := strings.IndexByte(domain, '.')
    if iDot == -1 || iDot == 0 || iDot == len(domain)-1 {
        return false
    }

    if strings.Index(domain, "..") != -1 {
        return false
    }

    iTLD := strings.LastIndexByte(domain, '.')
    return 2 <= len([]rune(domain[iTLD+1:]))
}

func containsWhitespace(v string) bool {
    for _, r := range v {
        if unicode.IsSpace(r) {
            return true
        }
    }
    return false
}

Upvotes: 2

Riyaz Khan
Riyaz Khan

Reputation: 3278

The above approach from @icza is nice, however, if we use it in login/signup form validation, many people will enter a partial or incorrect email address, which will create a bunch of invalid records in production. Furthermore, who knows we might get killed because of that😅.

Therefore, I went to the regex solution to validate standard emails:

Here is the code:

func isEmailValid(e string) bool {
    emailRegex := regexp.MustCompile(`^[a-z0-9._%+\-]+@[a-z0-9.\-]+\.[a-z]{2,4}$`)
    return emailRegex.MatchString(e)
}

Test Cases:

fmt.Println(isEmailValid("[email protected]"))         // true 
fmt.Println(isEmailValid("bad-email"))               // false
fmt.Println(isEmailValid("[email protected]"))      // false
fmt.Println(isEmailValid("test-email.com"))        // false
fmt.Println(isEmailValid("[email protected]"))  // true

Upvotes: 19

Shah
Shah

Reputation: 2894

Method implementation example:

var emailRegexp = 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])?)*$")

This was in the same package as where I created my struct

type EmailInputRegistration struct {
    Email           string
}

And then for handling errors:

func (in EmailInputRegistration) Validate() error {
    if !emailRegexp.MatchString(in.Email) {
        return fmt.Errorf("%w: email invalid", ErrValidation)
    }
    //any other exception handling...

    return nil
}

Ideally, this EmailInputRegistration should be refactored to include all the data needed for Registering such as email, user, password, etc.

Upvotes: 0

Related Questions