Repository: go-playground/validator
Pull request #804 (2021-07-28 14:02:09)

Add `boolean` baked-in validator to check a string value is a valid boolean representation
## Add `boolean` baked-in validator

Due to technical limitations upstream, some of our structs can only have `string` fields, but some of these fields will be parsed into booleans by the business logic, using `strconv.ParseBool`:

>ParseBool returns the boolean value represented by the string. It accepts 1, t, T, TRUE, true, True, 0, f, F, FALSE, false, False. Any other value returns an error. 

Similar to the `numeric` validator, this PR adds a `boolean` validator that checks that the field will be safely convertible to a boolean. Instead of enumerating all values currently accepted by `ParseBool`, this PR just calls the method and confirms it does not return an error. The rationale for this is to stay in sync if the accepted values change in future versions of go.

I updated the tests and documentation, but I might have forgotten something else to update.

**Make sure that you've checked the boxes below before you submit PR:**
- [x] Tests exist or have been written that cover this particular change.

@go-playground/validator-maintainers

coveralls: 
[![Coverage Status](https://coveralls.io/builds/41987544/badge)](https://coveralls.io/builds/41987544)

Coverage increased (+0.008%) to 75.1% when pulling **5ce08baf911141cd8201eb2eafbf1bf5dc28cc69 on xvello:feature/boolean** into **14221d02a510cc35db1ef6b8e418a3ca5f74ad69 on go-playground:master**.


Patch:
diff --git a/README.md b/README.md
index 1883cab..da5b0be 100644
--- a/README.md
+++ b/README.md
@@ -126,6 +126,7 @@ Baked-in Validations
 | alphanumunicode | Alphanumeric Unicode |
 | alphaunicode | Alpha Unicode |
 | ascii | ASCII |
+| boolean | Boolean |
 | contains | Contains |
 | containsany | Contains Any |
 | containsrune | Contains Rune |
diff --git a/baked_in.go b/baked_in.go
index ce81457..d175ecc 100644
--- a/baked_in.go
+++ b/baked_in.go
@@ -107,6 +107,7 @@ var (
 		"alphanum":                      isAlphanum,
 		"alphaunicode":                  isAlphaUnicode,
 		"alphanumunicode":               isAlphanumUnicode,
+		"boolean":                       isBoolean,
 		"numeric":                       isNumeric,
 		"number":                        isNumber,
 		"hexadecimal":                   isHexadecimal,
@@ -1438,6 +1439,12 @@ func isAlphaUnicode(fl FieldLevel) bool {
 	return alphaUnicodeRegex.MatchString(fl.Field().String())
 }
 
+// isBoolean is the validation function for validating if the current field's value can be safely converted to a boolean.
+func isBoolean(fl FieldLevel) bool {
+	_, err := strconv.ParseBool(fl.Field().String())
+	return err == nil
+}
+
 // isDefault is the opposite of required aka hasValue
 func isDefault(fl FieldLevel) bool {
 	return !hasValue(fl)
diff --git a/doc.go b/doc.go
index cf1376b..864fc84 100644
--- a/doc.go
+++ b/doc.go
@@ -726,6 +726,12 @@ This validates that a string value contains unicode alphanumeric characters only
 
 	Usage: alphanumunicode
 
+Boolean
+
+This validates that a string value can successfully be parsed into a boolean with strconv.ParseBool
+
+	Usage: boolean
+
 Number
 
 This validates that a string value contains number values only.


Test patch:
diff --git a/validator_test.go b/validator_test.go
index 3f4864b..fcbe6a0 100644
--- a/validator_test.go
+++ b/validator_test.go
@@ -71,6 +71,7 @@ type TestString struct {
 	Gt        string `validate:"gt=10"`
 	Gte       string `validate:"gte=10"`
 	OmitEmpty string `validate:"omitempty,min=1,max=10"`
+	Boolean   string `validate:"boolean"`
 	Sub       *SubTest
 	SubIgnore *SubTest `validate:"-"`
 	Anonymous struct {
@@ -7943,6 +7944,7 @@ func TestStructStringValidation(t *testing.T) {
 		Lte:       "0123456789",
 		Gt:        "01234567890",
 		Gte:       "0123456789",
+		Boolean:   "true",
 		OmitEmpty: "",
 		Sub: &SubTest{
 			Test: "1",
@@ -7974,6 +7976,7 @@ func TestStructStringValidation(t *testing.T) {
 		Gt:        "1",
 		Gte:       "1",
 		OmitEmpty: "12345678901",
+		Boolean:   "nope",
 		Sub: &SubTest{
 			Test: "",
 		},
@@ -7991,7 +7994,7 @@ func TestStructStringValidation(t *testing.T) {
 
 	// Assert Top Level
 	NotEqual(t, errs, nil)
-	Equal(t, len(errs.(ValidationErrors)), 13)
+	Equal(t, len(errs.(ValidationErrors)), 14)
 
 	// Assert Fields
 	AssertError(t, errs, "TestString.Required", "TestString.Required", "Required", "Required", "required")
@@ -8004,6 +8007,7 @@ func TestStructStringValidation(t *testing.T) {
 	AssertError(t, errs, "TestString.Gt", "TestString.Gt", "Gt", "Gt", "gt")
 	AssertError(t, errs, "TestString.Gte", "TestString.Gte", "Gte", "Gte", "gte")
 	AssertError(t, errs, "TestString.OmitEmpty", "TestString.OmitEmpty", "OmitEmpty", "OmitEmpty", "max")
+	AssertError(t, errs, "TestString.Boolean", "TestString.Boolean", "Boolean", "Boolean", "boolean")
 
 	// Nested Struct Field Errs
 	AssertError(t, errs, "TestString.Anonymous.A", "TestString.Anonymous.A", "A", "A", "required")