...

Source file src/github.com/go-playground/validator/v10/_examples/struct-level/main.go

Documentation: github.com/go-playground/validator/v10/_examples/struct-level

     1  package main
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"reflect"
     7  	"strings"
     8  
     9  	"github.com/go-playground/validator/v10"
    10  )
    11  
    12  type validationError struct {
    13  	Namespace       string `json:"namespace"` // can differ when a custom TagNameFunc is registered or
    14  	Field           string `json:"field"`     // by passing alt name to ReportError like below
    15  	StructNamespace string `json:"structNamespace"`
    16  	StructField     string `json:"structField"`
    17  	Tag             string `json:"tag"`
    18  	ActualTag       string `json:"actualTag"`
    19  	Kind            string `json:"kind"`
    20  	Type            string `json:"type"`
    21  	Value           string `json:"value"`
    22  	Param           string `json:"param"`
    23  	Message         string `json:"message"`
    24  }
    25  
    26  type Gender uint
    27  
    28  const (
    29  	Male Gender = iota + 1
    30  	Female
    31  	Intersex
    32  )
    33  
    34  func (gender Gender) String() string {
    35  	terms := []string{"Male", "Female", "Intersex"}
    36  	if gender < Male || gender > Intersex {
    37  		return "unknown"
    38  	}
    39  	return terms[gender]
    40  }
    41  
    42  // User contains user information
    43  type User struct {
    44  	FirstName      string     `json:"fname"`
    45  	LastName       string     `json:"lname"`
    46  	Age            uint8      `validate:"gte=0,lte=130"`
    47  	Email          string     `json:"e-mail" validate:"required,email"`
    48  	FavouriteColor string     `validate:"hexcolor|rgb|rgba"`
    49  	Addresses      []*Address `validate:"required,dive,required"` // a person can have a home and cottage...
    50  	Gender         Gender     `json:"gender" validate:"required,gender_custom_validation"`
    51  }
    52  
    53  // Address houses a users address information
    54  type Address struct {
    55  	Street string `validate:"required"`
    56  	City   string `validate:"required"`
    57  	Planet string `validate:"required"`
    58  	Phone  string `validate:"required"`
    59  }
    60  
    61  // use a single instance of Validate, it caches struct info
    62  var validate *validator.Validate
    63  
    64  func main() {
    65  
    66  	validate = validator.New()
    67  
    68  	// register function to get tag name from json tags.
    69  	validate.RegisterTagNameFunc(func(fld reflect.StructField) string {
    70  		name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]
    71  		if name == "-" {
    72  			return ""
    73  		}
    74  		return name
    75  	})
    76  
    77  	// register validation for 'User'
    78  	// NOTE: only have to register a non-pointer type for 'User', validator
    79  	// internally dereferences during it's type checks.
    80  	validate.RegisterStructValidation(UserStructLevelValidation, User{})
    81  
    82  	// register a custom validation for user genre on a line
    83  	// validates that an enum is within the interval
    84  	err := validate.RegisterValidation("gender_custom_validation", func(fl validator.FieldLevel) bool {
    85  		value := fl.Field().Interface().(Gender)
    86  		return value.String() != "unknown"
    87  	})
    88  	if err != nil {
    89  		fmt.Println(err)
    90  		return
    91  	}
    92  
    93  	// build 'User' info, normally posted data etc...
    94  	address := &Address{
    95  		Street: "Eavesdown Docks",
    96  		Planet: "Persphone",
    97  		Phone:  "none",
    98  		City:   "Unknown",
    99  	}
   100  
   101  	user := &User{
   102  		FirstName:      "",
   103  		LastName:       "",
   104  		Age:            45,
   105  		Email:          "Badger.Smith@gmail",
   106  		FavouriteColor: "#000",
   107  		Addresses:      []*Address{address},
   108  	}
   109  
   110  	// returns InvalidValidationError for bad validation input, nil or ValidationErrors ( []FieldError )
   111  	err = validate.Struct(user)
   112  	if err != nil {
   113  
   114  		// this check is only needed when your code could produce
   115  		// an invalid value for validation such as interface with nil
   116  		// value most including myself do not usually have code like this.
   117  		if _, ok := err.(*validator.InvalidValidationError); ok {
   118  			fmt.Println(err)
   119  			return
   120  		}
   121  
   122  		for _, err := range err.(validator.ValidationErrors) {
   123  			e := validationError{
   124  				Namespace:       err.Namespace(),
   125  				Field:           err.Field(),
   126  				StructNamespace: err.StructNamespace(),
   127  				StructField:     err.StructField(),
   128  				Tag:             err.Tag(),
   129  				ActualTag:       err.ActualTag(),
   130  				Kind:            fmt.Sprintf("%v", err.Kind()),
   131  				Type:            fmt.Sprintf("%v", err.Type()),
   132  				Value:           fmt.Sprintf("%v", err.Value()),
   133  				Param:           err.Param(),
   134  				Message:         err.Error(),
   135  			}
   136  
   137  			indent, err := json.MarshalIndent(e, "", "  ")
   138  			if err != nil {
   139  				fmt.Println(err)
   140  				panic(err)
   141  			}
   142  
   143  			fmt.Println(string(indent))
   144  		}
   145  
   146  		// from here you can create your own error messages in whatever language you wish
   147  		return
   148  	}
   149  
   150  	// save user to database
   151  }
   152  
   153  // UserStructLevelValidation contains custom struct level validations that don't always
   154  // make sense at the field validation level. For Example this function validates that either
   155  // FirstName or LastName exist; could have done that with a custom field validation but then
   156  // would have had to add it to both fields duplicating the logic + overhead, this way it's
   157  // only validated once.
   158  //
   159  // NOTE: you may ask why wouldn't I just do this outside of validator, because doing this way
   160  // hooks right into validator and you can combine with validation tags and still have a
   161  // common error output format.
   162  func UserStructLevelValidation(sl validator.StructLevel) {
   163  
   164  	user := sl.Current().Interface().(User)
   165  
   166  	if len(user.FirstName) == 0 && len(user.LastName) == 0 {
   167  		sl.ReportError(user.FirstName, "fname", "FirstName", "fnameorlname", "")
   168  		sl.ReportError(user.LastName, "lname", "LastName", "fnameorlname", "")
   169  	}
   170  
   171  	// plus can do more, even with different tag than "fnameorlname"
   172  }
   173  

View as plain text