From 4c1bd614e306e580fff06cd83334f30d8e35db8c Mon Sep 17 00:00:00 2001 From: Josh Glazebrook Date: Sat, 4 Nov 2023 10:34:26 -0500 Subject: [PATCH] Add support for validating against uuid values that are structs which implement the Stringer interface. (#1189) ## Fixes Or Enhances This adds the ability to validate UUIDs that have an underlying struct value but also implement the Stringer interface. This should cover most UUID implementations (google's uuid, etc). Implements: #900, specifically https://github.com/go-playground/validator/issues/900#issuecomment-1046030775. **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 --- baked_in.go | 18 +++++++++--------- util.go | 11 +++++++++++ validator_test.go | 29 +++++++++++++++++++++++++++++ 3 files changed, 49 insertions(+), 9 deletions(-) diff --git a/baked_in.go b/baked_in.go index 0b6233070..19fa415ed 100644 --- a/baked_in.go +++ b/baked_in.go @@ -508,47 +508,47 @@ func isASCII(fl FieldLevel) bool { // isUUID5 is the validation function for validating if the field's value is a valid v5 UUID. func isUUID5(fl FieldLevel) bool { - return uUID5Regex.MatchString(fl.Field().String()) + return fieldMatchesRegexByStringerValOrString(uUID5Regex, fl) } // isUUID4 is the validation function for validating if the field's value is a valid v4 UUID. func isUUID4(fl FieldLevel) bool { - return uUID4Regex.MatchString(fl.Field().String()) + return fieldMatchesRegexByStringerValOrString(uUID4Regex, fl) } // isUUID3 is the validation function for validating if the field's value is a valid v3 UUID. func isUUID3(fl FieldLevel) bool { - return uUID3Regex.MatchString(fl.Field().String()) + return fieldMatchesRegexByStringerValOrString(uUID3Regex, fl) } // isUUID is the validation function for validating if the field's value is a valid UUID of any version. func isUUID(fl FieldLevel) bool { - return uUIDRegex.MatchString(fl.Field().String()) + return fieldMatchesRegexByStringerValOrString(uUIDRegex, fl) } // isUUID5RFC4122 is the validation function for validating if the field's value is a valid RFC4122 v5 UUID. func isUUID5RFC4122(fl FieldLevel) bool { - return uUID5RFC4122Regex.MatchString(fl.Field().String()) + return fieldMatchesRegexByStringerValOrString(uUID5RFC4122Regex, fl) } // isUUID4RFC4122 is the validation function for validating if the field's value is a valid RFC4122 v4 UUID. func isUUID4RFC4122(fl FieldLevel) bool { - return uUID4RFC4122Regex.MatchString(fl.Field().String()) + return fieldMatchesRegexByStringerValOrString(uUID4RFC4122Regex, fl) } // isUUID3RFC4122 is the validation function for validating if the field's value is a valid RFC4122 v3 UUID. func isUUID3RFC4122(fl FieldLevel) bool { - return uUID3RFC4122Regex.MatchString(fl.Field().String()) + return fieldMatchesRegexByStringerValOrString(uUID3RFC4122Regex, fl) } // isUUIDRFC4122 is the validation function for validating if the field's value is a valid RFC4122 UUID of any version. func isUUIDRFC4122(fl FieldLevel) bool { - return uUIDRFC4122Regex.MatchString(fl.Field().String()) + return fieldMatchesRegexByStringerValOrString(uUIDRFC4122Regex, fl) } // isULID is the validation function for validating if the field's value is a valid ULID. func isULID(fl FieldLevel) bool { - return uLIDRegex.MatchString(fl.Field().String()) + return fieldMatchesRegexByStringerValOrString(uLIDRegex, fl) } // isMD4 is the validation function for validating if the field's value is a valid MD4. diff --git a/util.go b/util.go index 4bd947bdf..c8b901dbd 100644 --- a/util.go +++ b/util.go @@ -1,7 +1,9 @@ package validator import ( + "fmt" "reflect" + "regexp" "strconv" "strings" "time" @@ -292,3 +294,12 @@ func panicIf(err error) { panic(err.Error()) } } + +// Checks if field value matches regex. If fl.Field can be cast to Stringer, it uses the Stringer interfaces +// String() return value. Otherwise, it uses fl.Field's String() value. +func fieldMatchesRegexByStringerValOrString(regex *regexp.Regexp, fl FieldLevel) bool { + if stringer, ok := fl.Field().Interface().(fmt.Stringer); ok { + return regex.MatchString(stringer.String()) + } + return regex.MatchString(fl.Field().String()) +} diff --git a/validator_test.go b/validator_test.go index 230ab61b9..b5949ac70 100644 --- a/validator_test.go +++ b/validator_test.go @@ -4105,6 +4105,16 @@ func TestUUID3Validation(t *testing.T) { } } +type uuidTestType struct { + val string +} + +func (u uuidTestType) String() string { + return u.val +} + +var _ fmt.Stringer = uuidTestType{} + func TestUUIDValidation(t *testing.T) { tests := []struct { param string @@ -4141,6 +4151,25 @@ func TestUUIDValidation(t *testing.T) { } } } + + // Test UUID validation on uuid structs type that implements Stringer interface. + structWithValidUUID := struct { + UUID uuidTestType `validate:"uuid"` + }{ + UUID: uuidTestType{val: "a987fbc9-4bed-3078-cf07-9141ba07c9f3"}, + } + structWithInvalidUUID := struct { + UUID uuidTestType `validate:"uuid"` + }{ + UUID: uuidTestType{val: "934859"}, + } + + if err := validate.Struct(structWithValidUUID); err != nil { + t.Fatalf("UUID failed Error: %s", err) + } + if err := validate.Struct(structWithInvalidUUID); err == nil { + t.Fatal("UUID failed Error expected but received nil") + } } func TestUUID5RFC4122Validation(t *testing.T) {