I am trying to validate a struct field in Golang using the go-playground/validator/v10 package. Specifically, I want to use the oneof tag to validate that the field value matches one of the predefined values, which includes strings with single quotes ('). Here's the code I am using:
package main
import (
"fmt"
"github/go-playground/validator/v10"
)
// Struct with validation tag
type Award struct {
Title string `validate:"oneof=palm'dor level'dor 'state award'"`
}
func main() {
validate := validator.New()
// Test cases
testCases := []Award{
{"palm'dor"}, // Expected: Valid
{"level'dor"}, // Expected: Valid
{"state award"}, // Expected: Valid
{"other"}, // Expected: Invalid
}
for _, testCase := range testCases {
err := validate.Struct(testCase)
if err != nil {
fmt.Printf("Input: %q - Invalid (%v)\n", testCase.Title, err)
} else {
fmt.Printf("Input: %q - Valid\n", testCase.Title)
}
}
}
Expected Behavior: The program should validate "palm'dor", "level'dor", and "state award" as valid inputs. Any other value should be marked as invalid.
Problem: When I run the program, I got the following output:
Input: "palm'dor" - Invalid (Key: 'Award.Title' Error:Field validation for 'Title' failed on the 'oneof' tag)
Input: "level'dor" - Invalid (Key: 'Award.Title' Error:Field validation for 'Title' failed on the 'oneof' tag)
Input: "state award" - Valid
Input: "other" - Invalid (Key: 'Award.Title' Error:Field validation for 'Title' failed on the 'oneof' tag)
I am trying to validate a struct field in Golang using the go-playground/validator/v10 package. Specifically, I want to use the oneof tag to validate that the field value matches one of the predefined values, which includes strings with single quotes ('). Here's the code I am using:
package main
import (
"fmt"
"github.com/go-playground/validator/v10"
)
// Struct with validation tag
type Award struct {
Title string `validate:"oneof=palm'dor level'dor 'state award'"`
}
func main() {
validate := validator.New()
// Test cases
testCases := []Award{
{"palm'dor"}, // Expected: Valid
{"level'dor"}, // Expected: Valid
{"state award"}, // Expected: Valid
{"other"}, // Expected: Invalid
}
for _, testCase := range testCases {
err := validate.Struct(testCase)
if err != nil {
fmt.Printf("Input: %q - Invalid (%v)\n", testCase.Title, err)
} else {
fmt.Printf("Input: %q - Valid\n", testCase.Title)
}
}
}
Expected Behavior: The program should validate "palm'dor", "level'dor", and "state award" as valid inputs. Any other value should be marked as invalid.
Problem: When I run the program, I got the following output:
Input: "palm'dor" - Invalid (Key: 'Award.Title' Error:Field validation for 'Title' failed on the 'oneof' tag)
Input: "level'dor" - Invalid (Key: 'Award.Title' Error:Field validation for 'Title' failed on the 'oneof' tag)
Input: "state award" - Valid
Input: "other" - Invalid (Key: 'Award.Title' Error:Field validation for 'Title' failed on the 'oneof' tag)
Seems there is a known issue reported in go-playground/validator, Issue #1350, that explains the existing tokenizer regex doesn't work well with multi-word strings that contain a single quote character.
Until the point, the package maintainer comes up with an effective solution, I suggest creating a custom validator, that does a direct string comparison. It is particularly effective when there are special characters involved and also be easily extended to include additional validation rules.
package main
import (
"fmt"
"slices"
"github.com/go-playground/validator/v10"
)
type Award struct {
Title string `validate:"validAward"`
}
func main() {
validate := validator.New()
// Register custom validator
validate.RegisterValidation("validAward", validateAward)
testCases := []Award{
{"palm'dor"},
{"level'dor"},
{"state award"},
{"other"},
}
for _, testCase := range testCases {
err := validate.Struct(testCase)
if err != nil {
fmt.Printf("Input: %q - Invalid (%v)\n", testCase.Title, err)
} else {
fmt.Printf("Input: %q - Valid\n", testCase.Title)
}
}
}
func validateAward(fl validator.FieldLevel) bool {
value := fl.Field().String()
validAwards := []string{"palm'dor", "level'dor", "state award"}
return slices.Contains(validAwards, value)
}
which returns as expected.
Input: "palm'dor" - Valid
Input: "level'dor" - Valid
Input: "state award" - Valid
Input: "other" - Invalid (Key: 'Award.Title' Error:Field validation for 'Title' failed on the 'validAward' tag)
Based on the answer by @Inian and the official GitHub repo(go-playground/validator) issue. We could create our own custom validation function like oneOfCustom which will receive the allowed parameter list where single quotes(') can be escaped with one more single quote('').
// Parse the allowed values with the given regex
func parseAllowedValues(input string) ([]string, error) {
re, err := regexp.Compile(`'([^']|'')*'|\S+`)
if err != nil {
return nil, fmt.Errorf("failed to compile regex: %w", err)
}
matches := re.FindAllString(input, -1)
if matches == nil {
return nil, nil
}
for i, match := range matches {
if len(match) > 1 {
if (match[0] == '\'' && match[len(match)-1] == '\'') {
// Remove outer single quotes
match = match[1 : len(match)-1]
}
// Replace doubled single quotes with single quote
match = regexp.MustCompile("''").ReplaceAllString(match, "'")
}
matches[i] = match
}
return matches, nil
}
// Custom validation function
func oneofCustom(fl validator.FieldLevel) bool {
// Get the full tag value
tag := fl.Param()
// Parse the allowed values
allowedValues, err := parseAllowedValues(tag)
if err != nil {
fmt.Printf("Error parsing allowed values: %v\n", err)
return false
}
// Get the field value to validate
fieldValue := fl.Field().String()
// Check if the field value is in the allowed list
for _, v := range allowedValues {
if fieldValue == v {
return true
}
}
return false
}
Try it on the Go Playground.