Data Validation

Data validators, like go-playground/validator utilize struct tags, which are intuitive and concise. I'd like to implement a simple validator for fun. For production use, it's best to choose a library.

Write a Validator

If you haven't initialized Go modules, do so with:

$ go mod init wdgfs

Then, add validation/main.go:

package validation

import (
	"fmt"
	"reflect"
	"regexp"
	"strings"
)

var validators = map[string]func(param string, value interface{}) bool{
	"required": func(param string, value interface{}) bool {
		return value != nil
	},
	"email": func(param string, value interface{}) bool {
		if val, ok := value.(string); ok {
			pattern := regexp.MustCompile(`^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$`)
			return pattern.MatchString(val)
		}
		return false
	},
}

func Validate(data interface{}) error {
	val := reflect.ValueOf(data)
	for i := range val.NumField() {
		field := val.Type().Field(i)
		for _, rule := range strings.Split(field.Tag.Get("validate"), ",") {
			if validator, ok := validators[rule]; ok {
				if !validator(rule, val.Field(i).Interface()) {
					return fmt.Errorf("%s failed validation rule %s", field.Name, rule)
				}
			} else {
				return fmt.Errorf("invalide validation rule %s", rule)
			}
		}
	}
	return nil
}

Use the Validator

Update server.go

package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"wdgfs/validation"
)

type User struct {
	Name  string `validate:"required"`
	Email string `validate:"required,email"`
}

func main() {

	http.HandleFunc("POST /users", func(w http.ResponseWriter, r *http.Request) {
		var p User

		if err := json.NewDecoder(r.Body).Decode(&p); err != nil {
			http.Error(w, "Invalid request payload", http.StatusBadRequest)
			return
		}

		if err := validation.Validate(p); err != nil {
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}

		fmt.Fprintf(w, "User: %+v", p)
	})

	if err := http.ListenAndServe(":3000", nil); err != nil {
		panic(err)
	}
}

Test the Validator

$ curl -i http://localhost:3000/users -d '{"name": "Alice", "email": "alice@test"}'
HTTP/1.1 400 Bad Request
Content-Type: text/plain; charset=utf-8
X-Content-Type-Options: nosniff
Date: Tue, 28 Oct 2024 01:13:21 GMT
Content-Length: 35

Email failed validation rule email
$ curl -i http://localhost:3000/users -d '{"name": "Alice", "email": "alice@test.co"}'
HTTP/1.1 200 OK
Date: Tue, 28 Oct 2024 01:13:25 GMT
Content-Length: 38
Content-Type: text/plain; charset=utf-8

User: {Name:Alice Email:alice@test.co}