diff --git a/baked_in.go b/baked_in.go index f7b55ab3d..1ba72bef7 100644 --- a/baked_in.go +++ b/baked_in.go @@ -12,6 +12,7 @@ import ( "net/url" "os" "reflect" + "regexp" "strconv" "strings" "sync" @@ -231,6 +232,7 @@ var ( "mongodb": isMongoDB, "cron": isCron, "spicedb": isSpiceDB, + "python_var": isPythonVar, } ) @@ -2878,3 +2880,55 @@ func isCron(fl FieldLevel) bool { cronString := fl.Field().String() return cronRegex.MatchString(cronString) } + +// check if string is a valid python variable name +func isPythonVar(fl FieldLevel) bool { + // Python keywords can not be used as variable names + // in future this list may be subject to ammendement, pruning, or other change + switch fl.Field().String() { + case + "False", + "await", + "else", + "import", + "pass", + "None", + "break", + "except", + "in", + "raise", + "True", + "class", + "finally", + "is", + "return", + "and", + "continue", + "for", + "lambda", + "try", + "as", + "def", + "from", + "nonlocal", + "while", + "assert", + "del", + "global", + "not", + "with", + "async", + "elif", + "if", + "or", + "yield": + return false + } + + // all variables must start with a letter or underscore + // further chars can be alphanumeric, or underscore + // dashes and special characters are not allowed + pattern := regexp.MustCompile("^[a-zA-Z_][a-zA-Z0-9_]*$") + + return pattern.Match([]byte(fl.Field().String())) +} diff --git a/validator_test.go b/validator_test.go index 088782784..f1eb1c633 100644 --- a/validator_test.go +++ b/validator_test.go @@ -13311,6 +13311,41 @@ func TestCronExpressionValidation(t *testing.T) { } } +func TestPythonVariableValidation(t *testing.T) { + tests := []struct { + value string `validate:"python_var"` + tag string + expected bool + }{ + {"x", "python_var", true}, + {"x0", "python_var", true}, + {"x_0", "python_var", true}, + {"__init__", "python_var", true}, + {"x-x", "python_var", false}, + {"0x", "python_var", false}, + {"abcdefghijklmnopqrstuvwxyz", "python_var", true}, + {"abcde_0_0466___", "python_var", true}, + {"while", "python_var", false}, + {"False", "python_var", false}, + {"(x)", "python_var", false}, + } + + validate := New() + + for i, test := range tests { + errs := validate.Var(test.value, test.tag) + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf(`Index: %d python_var "%s" failed Error: %s`, i, test.value, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf(`Index: %d python_var "%s" should have errs`, i, test.value) + } + } + } +} + func TestNestedStructValidation(t *testing.T) { validator := New()