Skip to content

Instantly share code, notes, and snippets.

@gorshkov-leonid
Forked from sosedoff/1_simple.go
Created September 3, 2021 09:31
Show Gist options
  • Save gorshkov-leonid/3d1493de1d99043046ab6a1304268f40 to your computer and use it in GitHub Desktop.
Save gorshkov-leonid/3d1493de1d99043046ab6a1304268f40 to your computer and use it in GitHub Desktop.
Golang Custom Struct Tags Example
package main
import (
"fmt"
"reflect"
)
// Name of the struct tag used in examples
const tagName = "validate"
type User struct {
Id int `validate:"-"`
Name string `validate:"presence,min=2,max=32"`
Email string `validate:"email,required"`
}
func main() {
user := User{
Id: 1,
Name: "John Doe",
Email: "john@example",
}
// TypeOf returns the reflection Type that represents the dynamic type of variable.
// If variable is a nil interface value, TypeOf returns nil.
t := reflect.TypeOf(user)
// Get the type and kind of our user variable
fmt.Println("Type:", t.Name())
fmt.Println("Kind:", t.Kind())
// Iterate over all available fields and read the tag value
for i := 0; i < t.NumField(); i++ {
// Get the field, returns https://golang.org/pkg/reflect/#StructField
field := t.Field(i)
// Get the field tag value
tag := field.Tag.Get(tagName)
fmt.Printf("%d. %v (%v), tag: '%v'\n", i+1, field.Name, field.Type.Name(), tag)
}
}
package main
import (
"fmt"
"reflect"
"regexp"
"strings"
)
// Name of the struct tag used in examples.
const tagName = "validate"
// Regular expression to validate email address.
var mailRe = regexp.MustCompile(`\A[\w+\-.]+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z`)
// Generic data validator.
type Validator interface {
// Validate method performs validation and returns result and optional error.
Validate(interface{}) (bool, error)
}
// DefaultValidator does not perform any validations.
type DefaultValidator struct {
}
func (v DefaultValidator) Validate(val interface{}) (bool, error) {
return true, nil
}
// StringValidator validates string presence and/or its length.
type StringValidator struct {
Min int
Max int
}
func (v StringValidator) Validate(val interface{}) (bool, error) {
l := len(val.(string))
if l == 0 {
return false, fmt.Errorf("cannot be blank")
}
if l < v.Min {
return false, fmt.Errorf("should be at least %v chars long", v.Min)
}
if v.Max >= v.Min && l > v.Max {
return false, fmt.Errorf("should be less than %v chars long", v.Max)
}
return true, nil
}
// NumberValidator performs numerical value validation.
// Its limited to int type for simplicity.
type NumberValidator struct {
Min int
Max int
}
func (v NumberValidator) Validate(val interface{}) (bool, error) {
num := val.(int)
if num < v.Min {
return false, fmt.Errorf("should be greater than %v", v.Min)
}
if v.Max >= v.Min && num > v.Max {
return false, fmt.Errorf("should be less than %v", v.Max)
}
return true, nil
}
// EmailValidator checks if string is a valid email address.
type EmailValidator struct {
}
func (v EmailValidator) Validate(val interface{}) (bool, error) {
if !mailRe.MatchString(val.(string)) {
return false, fmt.Errorf("is not a valid email address")
}
return true, nil
}
// Returns validator struct corresponding to validation type
func getValidatorFromTag(tag string) Validator {
args := strings.Split(tag, ",")
switch args[0] {
case "number":
validator := NumberValidator{}
fmt.Sscanf(strings.Join(args[1:], ","), "min=%d,max=%d", &validator.Min, &validator.Max)
return validator
case "string":
validator := StringValidator{}
fmt.Sscanf(strings.Join(args[1:], ","), "min=%d,max=%d", &validator.Min, &validator.Max)
return validator
case "email":
return EmailValidator{}
}
return DefaultValidator{}
}
// Performs actual data validation using validator definitions on the struct
func validateStruct(s interface{}) []error {
errs := []error{}
// ValueOf returns a Value representing the run-time data
v := reflect.ValueOf(s)
for i := 0; i < v.NumField(); i++ {
// Get the field tag value
tag := v.Type().Field(i).Tag.Get(tagName)
// Skip if tag is not defined or ignored
if tag == "" || tag == "-" {
continue
}
// Get a validator that corresponds to a tag
validator := getValidatorFromTag(tag)
// Perform validation
valid, err := validator.Validate(v.Field(i).Interface())
// Append error to results
if !valid && err != nil {
errs = append(errs, fmt.Errorf("%s %s", v.Type().Field(i).Name, err.Error()))
}
}
return errs
}
type User struct {
Id int `validate:"number,min=1,max=1000"`
Name string `validate:"string,min=2,max=10"`
Bio string `validate:"string"`
Email string `validate:"email"`
}
func main() {
user := User{
Id: 0,
Name: "superlongstring",
Bio: "",
Email: "foobar",
}
fmt.Println("Errors:")
for i, err := range validateStruct(user) {
fmt.Printf("\t%d. %s\n", i+1, err.Error())
}
}
@gorshkov-leonid
Copy link
Author

gorshkov-leonid commented Sep 9, 2021

errors: best practice
https://banzaicloud.com/blog/error-handling-go/ https://github.com/emperror/emperror
https://www.bacancytechnology.com/blog/golang-error-handling phylosofy?
https://go.dev/blog/error-handling-and-go for dummies
https://go.dev/blog/errors-are-values from author !
https://eli.thegreenplace.net/2018/on-the-uses-and-misuses-of-panics-in-go/ panic or not panic
https://evilmartians.com/chronicles/errors-in-go-from-denial-to-acceptance from denial to acceptance !

  • https://go.googlesource.com/proposal/+/master/design/go2draft-error-handling.md draft
  • https://habr.com/ru/post/422049/ как пользоваться check in go 2
  • Также обратите внимание, что с точки зрения «пуриста» приведенные ниже примеры кода не представляют собой самый 
    идиоматичный Go. Я не придумал это полностью сам: источником вдохновения послужил исходный код Gin , популярного веб- 
    фреймворка во вселенной Go. В Gin, если во время обработки запроса возникает критическая ошибка, вы можете вызвать 
    panic(err)внутри обработчика, и Gin восстановится под капотом, зарегистрирует сообщение об ошибке и вернет статус 500 
    пользователю.
    
  • https://github.com/pkg/errors
  • https://github.com/hashicorp/go-multierror
  • https://pkg.go.dev/golang.org/x/sync/errgroup

https://golang.org/doc/go1.17 see New warnings for Is, As and Unwrap methods
https://ru.wikipedia.org/wiki/Go#%D0%9E%D0%B1%D1%80%D0%B0%D0%B1%D0%BE%D1%82%D0%BA%D0%B0_%D0%BE%D1%88%D0%B8%D0%B1%D0%BE%D0%BA_%D0%B8_%D0%B8%D1%81%D0%BA%D0%BB%D1%8E%D1%87%D0%B8%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D1%8B%D1%85_%D1%81%D0%B8%D1%82%D1%83%D0%B0%D1%86%D0%B8%D0%B9 wiki

https://medium.com/nuances-of-programming/%D0%BE%D0%B1%D1%80%D0%B0%D0%B1%D0%BE%D1%82%D0%BA%D0%B0-%D0%BE%D1%88%D0%B8%D0%B1%D0%BE%D0%BA-%D0%B2-golang-%D1%81-%D0%BF%D0%BE%D0%BC%D0%BE%D1%89%D1%8C%D1%8E-panic-defer-%D0%B8-recover-ea6bfd357af1 как работает recover

https://stackoverflow.com/questions/28745648/global-recover-handler-for-golang-http-panic mux recover
https://www.jajaldoang.com/post/handle-panic-in-http-server-using-middleware-go/ recover in all frameworks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment