Functional String Validation in Golang

03/12/2017 16:11 GMT

Is there a time, when you often get tired of writing the same if logic over and over again just to validate a simple string and you’re not a fan of using OO just for something dead simple. If so this little tutorial maybe for you, or not.

It’s going to be quite similar to Symfony’s raw value validation, the big different is that Symfony’s is OO, but my example is going to be a bit more functional with a little bit of OO, I’m using closures as I find that the more cleaner solution to the problem. To be fair Symfony’s is written in PHP and mine is written in Golang.

Okay, now I’m going to start off the tutorial, first off you need to create a error collector and a validation error type, this is very easy to do.

type ErrorCollector []error

func (e ErrorCollector) Error() string {
	str := []string{}
	for _, err := range e {
		str = append(str, fmt.Sprint(err))
	}
	return strings.Join(str, "\n")
}

func (e ErrorCollector) FilterErrors() error {
	errorCollection := ErrorCollector{}
	for _, err := range e {
		if nil != err {
			errorCollection = append(errorCollection, err)
		}
	}

	if len(errorCollection) == 0 {
		return nil
	}

	return errorCollection
}

type ValidationError string

func (e ValidationError) Error() string {
	return fmt.Sprint(string(e))
}

Both of those data type, implement the builtin error interface, so we can use those data type under the type hint of error easily.

Now for the next part, we are going to create a validation rule type, which is simply going to be a closures and we going to create the Validator function, which is a variadic function .

type StringValidationRule func(value *string, hasError bool) error

func StringValidator(value string, rules ...StringValidationRule) (string, error) {
	value = strings.TrimSpace(value)
	valuePtr := &value
	hasError := false

	errorCollection := ErrorCollector{}
	for _, rule := range rules {
		if err := rule(valuePtr, hasError); nil != err {
			hasError = true
			errorCollection = append(errorCollection, err)
		}
	}

	return value, errorCollection.FilterErrors()
}

As you can see working with a variadic function is dead simple, all you have to do is write a for … range loop and then handle the errors with a if logic. Now to create a couple of validation rules, this is also very easy.

func StringMaxRune(max int) StringValidationRule {
	return func(value *string, hasError bool) error {
		if utf8.RuneCountInString(*value) > max {
			return ValidationError(fmt.Sprintf("Value should not be more than '%d'", max))
		}

		return nil
	}
}

func StringMinRune(min int) StringValidationRule {
	return func(value *string, hasError bool) error {
		if utf8.RuneCountInString(*value) < min {
			return ValidationError(fmt.Sprintf("Value should be more than '%d'", min))
		}

		return nil
	}
}

As you can see, it just a function that returns a closure, that way you can reuse the same logic over and over again. Now to show you how to use them.

func main() {
	rules := []StringValidationRule{
		StringMinRune(2),
		StringMaxRune(4),
	}

	values := []string{"a", "cat", "dog", "horse"}
	for _, value := range values {
		value, err := StringValidator(value, rules...)
		fmt.Println("Value:", value)
		fmt.Println("Valid:", nil == err)
		fmt.Println("Error:", err)
	}
}

It’s looks pretty doesn’t it, if I do say so myself. Now for the output

Value: a
Valid: false
Error: Value should be more than '2'
Value: cat
Valid: true
Error: <nil>
Value: dog
Valid: true
Error: <nil>
Value: horse
Valid: false
Error: Value should not be more than '4'

And it’s works pretty well, it was remarkably dead simple. The right tool for the right job, does not just apply to programming languages, it’s also applies to concept such as OO, imperative and functional programming. I don’t hate Symfony or the OO concept, I just felt that OO is not the right tool for field validation, simply because I felt it’s can be done more elegantly with functional programming, of course that my opinion.

In PHP you can only have one constructor for the object and can only be written as a method, but Go on the other hand can have many constructor, it’s can be written as a function, method or even as a closure (which is very useful for mock testing) and yes can also use that string validation system inside the constructor, it’s awesome.

To be fair Symfony is not the only offender, I have seen bad examples in Go such as this they have made use of reflection, issue with that it’s does type checking at runtime rather than compilation time, so in my opinion using reflection for something as simple as field validation is not a very good use case, a good use case would be something like serialization and rendering html template, but not simple stuff like this, closure a better use case for that as type checking will done at compilation time.

The complete source code can found at https://github.com/CJ-Jackson/scratchpad/blob/master/validation/main.go

| |