-
Notifications
You must be signed in to change notification settings - Fork 1.4k
fix: resolve panic when using aliases with OR operator #1507
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
fix: resolve panic when using aliases with OR operator #1507
Conversation
b792454 to
df02f70
Compare
df02f70 to
b5ae5d3
Compare
zemzale
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generally looks like a good fix, just some style issues from me.
| } else if aliasTag, isAlias := v.aliases[current.tag]; isAlias { | ||
| // Handle alias within OR expression (issue #766) | ||
| // Recursively parse the alias tag | ||
| aliasFirst, aliasLast := v.parseFieldTagsRecursive(aliasTag, fieldName, current.tag, true) | ||
|
|
||
| // Copy the first parsed alias tag's validation data to current | ||
| current.tag = aliasFirst.tag | ||
| current.fn = aliasFirst.fn | ||
| current.runValidationWhenNil = aliasFirst.runValidationWhenNil | ||
| current.hasParam = aliasFirst.hasParam | ||
| current.param = aliasFirst.param | ||
| current.typeof = aliasFirst.typeof | ||
| current.hasAlias = true | ||
| // Note: isBlockEnd is NOT copied here; it will be set at the end of the OR loop | ||
|
|
||
| // If alias expanded to multiple tags, insert them into the chain | ||
| if aliasFirst.next != nil { | ||
| // Save what comes after current in the OR chain | ||
| nextInChain := current.next | ||
| // Link the rest of the alias chain | ||
| current.next = aliasFirst.next | ||
| // Connect the last alias tag to what was after current | ||
| aliasLast.next = nextInChain | ||
| // Clear isBlockEnd since this may not be the last tag in the OR expression | ||
| aliasLast.isBlockEnd = false | ||
| // Update current to point to the last tag in the alias chain | ||
| current = aliasLast | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comments are a bit excessive.
| } else if aliasTag, isAlias := v.aliases[current.tag]; isAlias { | |
| // Handle alias within OR expression (issue #766) | |
| // Recursively parse the alias tag | |
| aliasFirst, aliasLast := v.parseFieldTagsRecursive(aliasTag, fieldName, current.tag, true) | |
| // Copy the first parsed alias tag's validation data to current | |
| current.tag = aliasFirst.tag | |
| current.fn = aliasFirst.fn | |
| current.runValidationWhenNil = aliasFirst.runValidationWhenNil | |
| current.hasParam = aliasFirst.hasParam | |
| current.param = aliasFirst.param | |
| current.typeof = aliasFirst.typeof | |
| current.hasAlias = true | |
| // Note: isBlockEnd is NOT copied here; it will be set at the end of the OR loop | |
| // If alias expanded to multiple tags, insert them into the chain | |
| if aliasFirst.next != nil { | |
| // Save what comes after current in the OR chain | |
| nextInChain := current.next | |
| // Link the rest of the alias chain | |
| current.next = aliasFirst.next | |
| // Connect the last alias tag to what was after current | |
| aliasLast.next = nextInChain | |
| // Clear isBlockEnd since this may not be the last tag in the OR expression | |
| aliasLast.isBlockEnd = false | |
| // Update current to point to the last tag in the alias chain | |
| current = aliasLast | |
| } | |
| } else if aliasTag, isAlias := v.aliases[current.tag]; isAlias { | |
| // Handle alias within OR expression (issue #766) | |
| // Recursively parse the alias tag | |
| aliasFirst, aliasLast := v.parseFieldTagsRecursive(aliasTag, fieldName, current.tag, true) | |
| // Note: isBlockEnd is NOT copied here; it will be set at the end of the OR loop | |
| current.tag = aliasFirst.tag | |
| current.fn = aliasFirst.fn | |
| current.runValidationWhenNil = aliasFirst.runValidationWhenNil | |
| current.hasParam = aliasFirst.hasParam | |
| current.param = aliasFirst.param | |
| current.typeof = aliasFirst.typeof | |
| current.hasAlias = true | |
| // If alias expanded to multiple tags, insert them into the chain | |
| if aliasFirst.next != nil { | |
| nextInChain := current.next | |
| current.next = aliasFirst.next | |
| aliasLast.next = nextInChain | |
| aliasLast.isBlockEnd = false | |
| current = aliasLast | |
| } |
validator_test.go
Outdated
| // TestAliasWithOrOperator tests using registered aliases within OR expressions (issue #766) | ||
| func TestAliasWithOrOperator(t *testing.T) { | ||
| validate := New() | ||
|
|
||
| // Test 1: Built-in alias "iscolor" with OR - alias at end | ||
| type Test1 struct { | ||
| Field string `validate:"numeric|iscolor"` | ||
| } | ||
|
|
||
| t1 := Test1{Field: "#fff"} | ||
| errs := validate.Struct(t1) | ||
| Equal(t, errs, nil) // Should pass - valid hex color | ||
|
|
||
| t1.Field = "123" | ||
| errs = validate.Struct(t1) | ||
| Equal(t, errs, nil) // Should pass - numeric | ||
|
|
||
| t1.Field = "invalid!" | ||
| errs = validate.Struct(t1) | ||
| NotEqual(t, errs, nil) // Should fail - neither numeric nor color | ||
|
|
||
| // Test 2: Built-in alias with OR - alias at start | ||
| type Test2 struct { | ||
| Field string `validate:"iscolor|numeric"` | ||
| } | ||
|
|
||
| t2 := Test2{Field: "456"} | ||
| errs = validate.Struct(t2) | ||
| Equal(t, errs, nil) // Should pass - numeric | ||
|
|
||
| t2.Field = "rgb(255,0,0)" | ||
| errs = validate.Struct(t2) | ||
| Equal(t, errs, nil) // Should pass - valid rgb color | ||
|
|
||
| // Test 3: Custom alias with OR | ||
| validate.RegisterAlias("customnum", "numeric") | ||
|
|
||
| type Test3 struct { | ||
| Field string `validate:"alpha|customnum"` | ||
| } | ||
|
|
||
| t3 := Test3{Field: "789"} | ||
| errs = validate.Struct(t3) | ||
| Equal(t, errs, nil) // Should pass - numeric via alias | ||
|
|
||
| t3.Field = "abc" | ||
| errs = validate.Struct(t3) | ||
| Equal(t, errs, nil) // Should pass - alpha | ||
|
|
||
| // Test 4: Multiple aliases in OR | ||
| validate.RegisterAlias("customalpha", "alpha") | ||
|
|
||
| type Test4 struct { | ||
| Field string `validate:"customnum|customalpha"` | ||
| } | ||
|
|
||
| t4 := Test4{Field: "xyz"} | ||
| errs = validate.Struct(t4) | ||
| Equal(t, errs, nil) // Should pass - alpha via alias | ||
|
|
||
| // Test 5: Three-way OR with alias in middle | ||
| type Test5 struct { | ||
| Field string `validate:"alpha|customnum|email"` | ||
| } | ||
|
|
||
| t5 := Test5{Field: "test@example.com"} | ||
| errs = validate.Struct(t5) | ||
| Equal(t, errs, nil) // Should pass - email | ||
|
|
||
| // Test 6: Alias with parameter | ||
| validate.RegisterAlias("customgt5", "gt=5") | ||
|
|
||
| type Test6 struct { | ||
| Field int `validate:"eq=0|customgt5"` | ||
| } | ||
|
|
||
| t6 := Test6{Field: 0} | ||
| errs = validate.Struct(t6) | ||
| Equal(t, errs, nil) // Should pass - eq=0 | ||
|
|
||
| t6.Field = 10 | ||
| errs = validate.Struct(t6) | ||
| Equal(t, errs, nil) // Should pass - gt=5 via alias | ||
|
|
||
| t6.Field = 3 | ||
| errs = validate.Struct(t6) | ||
| NotEqual(t, errs, nil) // Should fail - not 0 and not > 5 | ||
|
|
||
| // Test 7: Another built-in alias with OR (country_code) | ||
| type Test7 struct { | ||
| Field string `validate:"numeric|country_code"` | ||
| } | ||
|
|
||
| t7 := Test7{Field: "US"} | ||
| errs = validate.Struct(t7) | ||
| Equal(t, errs, nil) // Should pass - valid country code | ||
| } | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again the comments are a bit too much. None of them are needed, or should just be used as names for t.Run subtests
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done! Removed all comments and converted tests to use t.Run subtests. @zemzale
Alias lookup was missing in OR parsing logic, causing panic when using aliases like `numeric|myalias`. Fixed by adding alias check before the undefined validation panic. Fixes go-playground#766
b5ae5d3 to
c3989b5
Compare
Fixes #766
Using aliases with OR operator causes panic:
The issue is that alias lookup only happens for comma-separated tags, not OR-separated ones. Fixed by adding alias check in the OR parsing logic.
Added tests for various cases (built-in aliases, custom aliases, different positions, etc).