Skip to content
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

How to validate struct without struct tag? #853

Open
2 tasks done
chenyanchen opened this issue Nov 11, 2021 · 6 comments
Open
2 tasks done

How to validate struct without struct tag? #853

chenyanchen opened this issue Nov 11, 2021 · 6 comments

Comments

@chenyanchen
Copy link

chenyanchen commented Nov 11, 2021

  • I have looked at the documentation here first?
  • 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
@chenyanchen
Copy link
Author

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

@chenyanchen chenyanchen changed the title How to validate a struct without tags? How to validate struct without struct tag? Dec 8, 2021
@zemzale
Copy link
Member

zemzale commented Jan 15, 2022

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 

@chenyanchen
Copy link
Author

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{})

@mattwelke
Copy link

mattwelke commented Mar 22, 2022

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.

@leoliang1997
Copy link
Contributor

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{})

I archieved this function,PR:#934

@paulmil1
Copy link

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants