go-playground / mold

:scissors: Is a general library to help modify or set data within data structures and other objects.
MIT License
227 stars 20 forks source link

Gin example #3

Open adrianrudnik opened 6 years ago

adrianrudnik commented 6 years ago

Do you have any example how to setup gin with v9 and modifiers/scrubbers?

Coming from other languages this seems like a nice way to handle it without writing a custom validator/processor for every user input. The only thing is, at least for me as a newish guy to golang, I have no clue how to integrate it with gin. The validator-upgrade.go is somewhat understandable, but I'm totally lost on how and where to hook into. Where does gin and the validator do the stuff and how do I inject mold.Transformer and actually execute it, prior to the validator (binding) run?

Can you give any hints or is there any help page I've missed?

adrianrudnik commented 6 years ago

Well that took a while to figure out.

type defaultValidator struct {
    once      sync.Once
    validate  *validator.Validate
    modifiers *mold.Transformer
}

and

func (v *defaultValidator) lazyinit() {
     ...
    v.modifiers = mold.New()
    v.modifiers.SetTagName("mod")
    v.modifiers.Register("trim", app.TrimSpace)
}

did the job.

Only other thing I could not solve, is how to actually use RegisterStructLevel without much code duplication. Wanted to wrap sql.NullString, as trim does not work (with RegisterCustomTypeFunc from the validator in mind) when used with this (because hard conversion to string in trim I assume). So it was easier to just rewrite TrimSpace with a type switch.

adrianrudnik commented 2 years ago

Oh my, I'm my own grave digger. Years later, same question, I even find my own answer that does not help myself. Is there an example how to register mold with current gin, current validator v10?

aupous commented 1 year ago

This is how I integrated mold with gin, u could check it out:

  1. Define your own Binding, similar to binding.JSONBinding of gin

    
    import (
    "context"
    "encoding/json"
    "errors"
    "io"
    "net/http"
    
    "github.com/gin-gonic/gin/binding"
    "github.com/go-playground/mold/v4/modifiers"
    )

var conform = modifiers.New()

// customJSONBinding is mostly same as Gin JSON Binding, but it transforms data after decoding and before validating type customJSONBinding struct{}

func (customJSONBinding) Name() string { return "supplier custom json binding" }

func (customJSONBinding) Bind(req *http.Request, obj any) error { if req == nil || req.Body == nil { return errors.New("invalid request") } return decodeJSON(req.Body, obj) }

func decodeJSON(r io.Reader, obj any) error { decoder := json.NewDecoder(r) if binding.EnableDecoderUseNumber { decoder.UseNumber() } if binding.EnableDecoderDisallowUnknownFields { decoder.DisallowUnknownFields() } if err := decoder.Decode(obj); err != nil { return err } if err := transform(obj); err != nil { return err } return validate(obj) }

func transform(obj any) error { if conform == nil { return nil } return conform.Struct(context.Background(), obj) }

func validate(obj any) error { if binding.Validator == nil { return nil } return binding.Validator.ValidateStruct(obj) }

2. Use your custom binding with `ShouldBindWith` to use `mold`
```go
// You have to init an instance of `customJSONBinding` to use
var customJSON = customJSONBinding{}

type Request struct {
    Email string `json:"email" mod:"trim" binding:"required,email"`
    Name string `json:"name" mod:"trim" binding:"required"`
}

func myHandler(c *gin.Context) {
    var req Request
    if err := c.ShouldBindWith(&req, customJSON); err != nil {
    // Do smt...
    }
}
victorybiz commented 1 year ago

The solution by @aupous worked.

But, here is how I integrated mold package with gin without rewriting/duplicating the entire binding package codes to create a custom binding for JSON and Form binding. I created a custom Validator to transform the data object before validating a struct and replacing the binding validator with my custom validator.

Create the custom validator:

//  helpers/custom_validator_helper.go
package helpers

import (
    "context"
    "reflect"
    "github.com/go-playground/mold/v4/modifiers"
    "github.com/go-playground/validator/v10"
)

type CustomValidator struct {
    validator *validator.Validate
}

func NewCustomValidator() *CustomValidator {
    v := validator.New()
    // Set the tag name to "binding", SetTagName allows for changing of the default tag name of 'validate'
    v.SetTagName("binding")
    // Register Tag Name Function to get json name as alternate names for StructFields.
    v.RegisterTagNameFunc(func(fld reflect.StructField) string {
        return fld.Tag.Get("json")
    })
    // Register custom validation tags if needed
    v.RegisterValidation("customValidation", customValidationFunc)
    return &CustomValidator{validator: v}
}

// ValidateStruct is called by Gin to validate the struct
func (cv *CustomValidator) ValidateStruct(obj interface{}) error {
    // transform the object using mold before validating the struct
    transformer := modifiers.New()
    if err := transformer.Struct(context.Background(), obj); err != nil {
        return err
    }
    // validate the struct
    if err := cv.validator.Struct(obj); err != nil {
        return err
    }
    return nil
}

// Engine is called by Gin to retrieve the underlying validation engine
func (cv *CustomValidator) Engine() interface{} {
    return cv.validator
}

// Custom validation function
func customValidationFunc(fl validator.FieldLevel) bool {
    // Custom validation logic here
    return true
}

Call the custom validator and override the binding validator:

// main.go
package main

import (
    "github.com/gin-gonic/gin"
    "github.com/gin-gonic/gin/binding"
)

func main() {
    router := gin.Default()

    // Use custom validator
    customValidator := helpers.NewCustomValidator() // Create a new instance of your custom validator
    binding.Validator = customValidator             // Set the binding.Validator to your custom validator

    // Define your routes and handlers
    // ...

    // Run the server
    router.Run(":8080")
}

Then bind your Struct to the request using any of the default binding method

type Request struct {
    Email string `json:"email" mod:"trim" binding:"required,email"`
    Name string `json:"name" mod:"trim" binding:"required"`
}

func myHandler(c *gin.Context) {
    var req Request
    if err := c.ShouldBind(&req); err != nil {
        // Do smt...
    }
}