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

Gin example #3

Open
adrianrudnik opened this issue Sep 2, 2018 · 4 comments · Fixed by #7
Open

Gin example #3

adrianrudnik opened this issue Sep 2, 2018 · 4 comments · Fixed by #7

Comments

@adrianrudnik
Copy link

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
Copy link
Author

adrianrudnik commented Sep 2, 2018

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
Copy link
Author

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?

@adrianrudnik adrianrudnik reopened this Oct 24, 2021
@aupous
Copy link

aupous commented May 15, 2023

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)
}
  1. Use your custom binding with ShouldBindWith to use mold
// 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
Copy link

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...
	}
}

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

Successfully merging a pull request may close this issue.

3 participants