validator
validator copied to clipboard
How to validate struct without struct tag?
- [x] I have looked at the documentation here first?
- [x] I have looked at the examples provided that may showcase my question here?
Package version v10:
import "github.com/go-playground/validator/v10"
Issue, Question or Enhancement:
I made some gRPC services with protobuf, the generated files has no struct tags. How to validate a struct without struct tags?
Maybe gogo/protobuf can add some struct tags into the generation files, but this is no matter to use a new package for the complex method.
I find the same problem #722 not same pain spot and closed.
There is some simply method to solove this problem?
Code sample, to showcase or reproduce:
.proto
file
message SearchRequest {
string RequestID = 1;
uint64 UserID = 2;
string Email = 3;
repeated string Keywords = 4;
}
Generated structure:
type SearchRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
RequestID string `protobuf:"bytes,1,opt,name=RequestID,proto3" json:"RequestID,omitempty"`
UserID uint64 `protobuf:"varint,2,opt,name=UserID,proto3" json:"UserID,omitempty"`
Email string `protobuf:"bytes,3,opt,name=Email,proto3" json:"Email,omitempty"`
Keywords []string `protobuf:"bytes,4,rep,name=Keywords,proto3" json:"Keywords,omitempty"`
}
I want validate this structure equal this:
type SearchRequest struct {
RequestID string `validate:"required"`
UserID uint64
Email string `validate:"omitempty,email"`
Keywords []string `validate:"dive,required"`
}
In the proto generated files, there is no tags, so I expect a method like RegisterStructMap
, the map[string]string
is map[FieldName]tag
.
func main() {
v := validator.New()
v.RegisterStructMap(
proto.SearchRequest{}, map[string]string{
"RequestID": "required",
"Email": "omitempty,email",
"Keywords": "dive,required",
},
)
req := &proto.SearchRequest{
Keywords: []string{""},
}
if err := v.Struct(req); err != nil {
fmt.Println(err)
}
}
// Output:
// Key: 'SearchRequest.RequestID' Error:Field validation for 'RequestID' failed on the 'required' tag
// Key: 'SearchRequest.Keywords[0]' Error:Field validation for 'Keywords[0]' failed on the 'required' tag
I find a way to validate structure without struct tag, is this the current best way to validate no tag structure?
This not my expect way to validate no tag structure.
package main
import (
"fmt"
"testing"
"github.com/go-playground/validator/v10"
)
var (
v = validator.New()
withTag = WithTag{
RequestID: "",
Email: "email",
Phone: "123456",
Keywords: []string{""},
}
noTag = NoTag(withTag)
)
func main() {
v.RegisterStructValidation(noTagValidateFunc, NoTag{})
fmt.Println(v.Struct(withTag))
fmt.Println(v.Struct(noTag))
}
type WithTag struct {
RequestID string `validate:"required"`
Email string `validate:"omitempty,email"`
Phone string `validate:"omitempty,e164"`
Keywords []string `validate:"dive,required"`
}
type NoTag struct {
RequestID string
Email string
Phone string
Keywords []string
}
func noTagValidateFunc(sl validator.StructLevel) {
s := sl.Current().Interface().(NoTag)
v := sl.Validator()
if err := v.Var(s.RequestID, "required"); err != nil {
sl.ReportValidationErrors("RequiredID", "RequiredID", err.(validator.ValidationErrors))
}
if err := v.Var(s.Email, "omitempty,email"); err != nil {
sl.ReportValidationErrors("Email", "Email", err.(validator.ValidationErrors))
}
if err := v.Var(s.Phone, "omitempty,e164"); err != nil {
sl.ReportValidationErrors("Phone", "Phone", err.(validator.ValidationErrors))
}
if err := v.Var(s.Keywords, "dive,required"); err != nil {
sl.ReportValidationErrors("Keywords", "Keywords", err.(validator.ValidationErrors))
}
}
func BenchmarkWithTag(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = v.Struct(withTag)
}
}
func BenchmarkNoTag(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = v.Struct(noTag)
}
}
And benchmark result:
$ go test -bench=. -benchmem .
goos: darwin
goarch: amd64
pkg: github.com/chenyanchen/test/validator
cpu: Intel(R) Core(TM) i5-1038NG7 CPU @ 2.00GHz
BenchmarkWithTag-8 515580 2031 ns/op 998 B/op 17 allocs/op
BenchmarkNoTag-8 6123906 200.5 ns/op 80 B/op 1 allocs/op
PASS
ok github.com/chenyanchen/test/validator 3.147s
Currently, there is nothing that would do this, except the painful way of using the Var
function.
Maybe it's worth exploring a path, where we can pass in validation rules as an argument to the validation function as we do with map validation https://pkg.go.dev/github.com/go-playground/validator/v10#Validate.ValidateMap
Would look something like
func (v *Validate) StructWithRules(s interface{}, rules map[string]interface{}) error
Currently, there is nothing that would do this, except the painful way of using the
Var
function.Maybe it's worth exploring a path, where we can pass in validation rules as an argument to the validation function as we do with map validation https://pkg.go.dev/github.com/go-playground/validator/v10#Validate.ValidateMap
Would look something like
func (v *Validate) StructWithRules(s interface{}, rules map[string]interface{}) error
I suggest RegisterStructValidationInMap
.
// rules is map[FieldName]Tag
func (v *Validate) RegisterStructValidationInMap(rules map[string]string, types ...interface{})
The way I would approach this problem would be to use two different structs for the different purposes. I would use the structs generated by protoc for my gRPC and Protobuf use cases (receiving data over the wire), map the data from the Protobuf struct to a new struct I create with the validation tags, perform the validation on this new struct with the data filled in, and use the result of that validation to decide what to do with the data in the Protobuf struct received over the wire.
Currently, there is nothing that would do this, except the painful way of using the
Var
function.Maybe it's worth exploring a path, where we can pass in validation rules as an argument to the validation function as we do with map validation https://pkg.go.dev/github.com/go-playground/validator/v10#Validate.ValidateMap
Would look something like
func (v *Validate) StructWithRules(s interface{}, rules map[string]interface{}) error
Currently, there is nothing that would do this, except the painful way of using the
Var
function. Maybe it's worth exploring a path, where we can pass in validation rules as an argument to the validation function as we do with map validation https://pkg.go.dev/github.com/go-playground/validator/v10#Validate.ValidateMap Would look something likefunc (v *Validate) StructWithRules(s interface{}, rules map[string]interface{}) error
I suggest
RegisterStructValidationInMap
.// rules is map[FieldName]Tag func (v *Validate) RegisterStructValidationInMap(rules map[string]string, types ...interface{})
I archieved this function,PR:#934
The way I would approach this problem would be to use two different structs for the different purposes. I would use the structs generated by protoc for my gRPC and Protobuf use cases (receiving data over the wire), map the data from the Protobuf struct to a new struct I create with the validation tags, perform the validation on this new struct with the data filled in, and use the result of that validation to decide what to do with the data in the Protobuf struct received over the wire.
^ boiler plate approach, not recommended