How to Create Custom Validator for ISO8601 Date with Go Validator from Go Playground and Echo Framework


I have requirement on my api that I want to build that need to parse ISO8601 formatted date and after I searched and learned about Go Validator features (https://github.com/go-playground/validator), I couldn’t find the built in validator for ISO8601 date. So after learned more about this validation library and also Echo (Echo is popular web framework by labstack.com) feature to integrate custom validator. I could make it work, and I will explain what I did in this article.

At first I need to create function that return bool value, and this function need to have capability to identify whether the parameter is valid ISO8601 date or not. I found that Go Validator can use regex to check whether parameter matches the specified regex rule so I will use this feature to create custom validator for Go Validator and integrate it with Echo framework.

So first time I created function to identify parameter is correct ISO8601 date by regex rule like below:

[go]
func IsISO8601Date(fl validator.FieldLevel) bool {
ISO8601DateRegexString := "^(-?(?:[1-9][0-9]*)?[0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])(?:T|\\s)(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])?(Z)?$"
ISO8601DateRegex := regexp.MustCompile(ISO8601DateRegexString)

return ISO8601DateRegex.MatchString(fl.Field().String())
}
[/go]

After that I need to create struct that will validate “before” and “after” parameter using the new “IsISO8601Date” function.
So I need to add the ISO8601date inside the DateParameters struct field tag for Before and After field.

[go]
type DateParameters struct {
Before string `query:"before" validate:"omitempty,ISO8601date"`
After string `query:"after" validate:"omitempty,ISO8601date"`
}
[/go]

On Echo handler I need to bind the parameters using DateParameters struct then call context Validate and pass the struct into it.

Using the new custom struct tag field, the validator will accept these pattern of date string for before and after parameter:

  • 2018-01-01T06:43:14
  • 2018-01-01T06:43:14Z
  • 2018-01-01 06:43:14Z
  • 2018-01-01 06:43:14

Below is the complete code:

[go]
package main

import (
"net/http"
"regexp"
"strings"

"github.com/labstack/echo"
"gopkg.in/go-playground/validator.v9"
)

/*
IsISO8601Date function to check parameter pattern for valid ISO8601 Date
*/
func IsISO8601Date(fl validator.FieldLevel) bool {
ISO8601DateRegexString := "^(-?(?:[1-9][0-9]*)?[0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])(?:T|\\s)(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])?(Z)?$"
ISO8601DateRegex := regexp.MustCompile(ISO8601DateRegexString)

return ISO8601DateRegex.MatchString(fl.Field().String())
}

/*CustomValidator struct is for storing the custom validator that will be registered to echo server */
type CustomValidator struct {
Validator *validator.Validate
}

/*
DateParameters struct is used to bind before and after parameters
*/
type DateParameters struct {
Before string `query:"before" validate:"omitempty,ISO8601date"`
After string `query:"after" validate:"omitempty,ISO8601date"`
}

/*
Validate is struct method that is called by registered validator in echo to validate
*/
func (cv *CustomValidator) Validate(i interface{}) error {
return cv.Validator.Struct(i)
}

/*ValidateDateHandler bind "before" and "after" from query parameters and check validity of those parameters */
func ValidateDateHandler(c echo.Context) error {
d := new(DateParameters)
var invalidParamsMessage []string

//bind the parameters to DateParameters struct
if err := c.Bind(d); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}

//run the validation and construct validation message
if err := c.Validate(d); err != nil {

for _, err := range err.(validator.ValidationErrors) {
//generate validation error message
invalidParamsMessage = append(invalidParamsMessage, err.Field())
}

return echo.NewHTTPError(http.StatusBadRequest, "Invalid parameter(s): "+strings.Join(invalidParamsMessage, ","))

}

return c.String(http.StatusOK, "VALID PARAMETERS")
}

func main() {
e := echo.New()

//create new validator
validator := validator.New()
//register the custom validation for ISISO8601 date
validator.RegisterValidation("ISO8601date", IsISO8601Date)
//link our custom validator to echo framework
e.Validator = &CustomValidator{validator}

e.GET("/", ValidateDateHandler)
// Start server
e.Logger.Fatal(e.Start(":8888"))
}

[/go]