HTTP Request Validation Middleware In Go - go

I am trying to create a common HTTP request validator middleware function that accepts type (maybe reflect.Type) as an argument and then using the package github.com/go-playground/validator/v10 to be able to unmarshall JSON into struct of mentioned type and validate the struct. I've tried to explain with the following example code...
EXAMPLE
type LoginRequestBody struct {
Username string `json:"username",validate:"required"`
Password string `json:"password",validate:"required"`
}
type SignupReqBody struct {
Username string `json:"username",validate:"required"`
Password string `json:"password",validate:"required"`
Age int `json:"age",validate:"required"`
}
// sample routers with a common middleware validator function
router.POST("/login", ReqValidate("LoginRequestBody"), LoginController)
router.POST("/signup", ReqValidate("SignupReqBody"), SignupController)
func ReqValidate(<something>) gin.HandlerFunc {
return func (c *gin.Context) {
// unmarshalling JSON into a struct
// common validation logic...
c.Next()
}
}
Overall, i wanna achieve the same validator flexibility as there in Node.js using Joi package.

I don't know if it is necessary to use middleware but I was recently trying to do something and I found an excellent tutorial that you can see here.
With Gin You can use binding:
Example:
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
type AnyStruct struct {
Price uint `json:"price" binding:"required,gte=10,lte=1000"`
}
func main() {
engine:=gin.New()
engine.POST("/test", func(context *gin.Context) {
body:=AnyStruct{}
if err:=context.ShouldBindJSON(&body);err!=nil{
context.AbortWithStatusJSON(http.StatusBadRequest,
gin.H{
"error": "VALIDATEERR-1",
"message": "Invalid inputs. Please check your inputs"})
return
}
context.JSON(http.StatusAccepted,&body)
})
engine.Run(":3000")
}

Don't use commas to separate struct tag key-value pairs, use space.
You can use generics (type parameters) to replace <something> but your controllers need to have the concrete type as their argument.
For example:
func ReqValidate[T any](next func(*gin.Context, *T)) gin.HandlerFunc {
return func(c *gin.Context) {
params := new(T)
if err := c.ShouldBindJSON(params); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
next(c, params)
}
}
And then the controllers:
type LoginRequestBody struct {
Username string `json:"username" validate:"required"`
Password string `json:"password" validate:"required"`
}
func LoginController(c *gin.Context, params *LoginRequestBody) {
// ...
}
type SignupReqBody struct {
Username string `json:"username" validate:"required"`
Password string `json:"password" validate:"required"`
Age int `json:"age" validate:"required"`
}
func SignupController(c *gin.Context, params *SignupReqBody) {
// ...
}
And then the routing:
router := gin.Default()
router.POST("/login", ReqValidate(LoginController))
router.POST("/signup", ReqValidate(SignupController))

Related

Validation in GO "ozzo-validation"

I'm nob in GO :) just try to create simple crud throw it using gin and plugin called ozzo-validation
My code:
package models
import (
validation "github.com/go-ozzo/ozzo-validation"
"gorm.io/gorm"
)
type Post struct {
gorm.Model
Name string `gorm:"type:varchar(255);"`
Desc string `gorm:"type:varchar(500);"`
}
type PostData struct {
Name string `json:"name"`
Desc string `json:"desc"`
}
func (p PostData) Validate() error {
return validation.ValidateStruct(&p,
validation.Field(&p.Name, validation.Required, validation.Length(5, 20)),
validation.Field(&p.Desc, validation.Required),
)
}
PostController:
package controllers
import (
"curd/app/models"
"fmt"
"github.com/gin-gonic/gin"
)
func Store(c *gin.Context) {
// Validate Input
var post models.PostData
err := post.Validate()
fmt.Println(err)
}
{
"name": "sdfsdfsfsdf"
}
The problem is once I submit the above JSON data from the postman the validation gives me this in terminal :
desc: cannot be blank; name: cannot be blank.
As noted in the comments, you need to decode the data from the HTTP request into the struct before you can perform validation. The validation errors you're seeing are a product of calling Validate() on a fresh instance (with zero values in every field) of your Post struct. Try this.
func Store(c *gin.Context) {
var post models.PostData
// This will infer what binder to use depending on the content-type header.
if err := c.ShouldBind(&post); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// Validate Input
err := post.Validate()
fmt.Println(err)
}

Golang Gin retrieve integer data

I'm trying to retrieve int data from POST requisitions with Gin, but I'm getting an error saying that the functions (PostForm, or any other) expects strings as arguments. I've tried to search for a function expecting int content, but with no success.
I have a struct do define the content, see the code below.
package userInfo
import(
"net/http"
"github.com/gin-gonic/gin"
)
type Person struct {
Name string
Age int
}
func ReturnPostContent(c *gin.Context){
var user Person
user.Name = c.PostForm("name")
user.Age = c.PostForm("age")
c.JSON(http.StatusOK, gin.H{
"user_name": user.Name,
"user_age": user.Age,
})
}
I was thinking in converting the value to int, but if I have 10 inputs this becomes very difficult and impractible.
The error from user.Age:
cannot use c.PostForm("age") (value of type string) as int value in assignmentcompiler
After a lot of source code reading, I finally found out that all you need is to add a 'form' tag on the required field:
Age int `form:"age"`
user strconv.Atoi(c.PostForm("age"))
complete code:
Person:
type Person struct {
Name string
Age int
}
r.POST("/profile", func(c *gin.Context) {
profile := new(Person)
profile.Name = c.PostForm("name")
profile.Age, _ = strconv.Atoi(c.PostForm("age"))
response := gin.H{
"user_name": profile.Name,
"user_age": profile.Age,
}
c.JSON(http.StatusOK, response)
})

Gin framework can not get the data from Postman

Gin framework can not get the data from Postman,below is a demo:
package main
import (
"fmt"
"github.com/gin-gonic/gin"
)
type CreateRequest struct {
Username string `json:"username"`
Phone string `json:"phone"`
Password string `json:"password"`
}
func Signup(c *gin.Context) {
var r CreateRequest
if err := c.Bind(&r); err != nil {
fmt.Println(err)
}
fmt.Println("this is debug info:start")
fmt.Println(r.Username)
fmt.Println("this is debug info:end")
}
func main() {
r := gin.Default()
r.POST("/signup", Signup)
r.Run() // listen and serve on 0.0.0.0:8080
}
Send a request from postman:
Debug info:
Where is the problem?
What should I do?
Accordingly to your screen it looks like you're sending x-www-form-urlencoded request and it this case you have to have form tags in your CreateRequest struct:
type CreateRequest struct {
Username string `json:"username" form:"username"`
Phone string `json:"phone" form:"phone"`
Password string `json:"password" form:"password"`
}
You should use form:"username" in order to POST using URI. Unless you want to POST it using body JSON, then you can only set json:"username" in your struct.

Dependency injection with interface in go

I have a struct StructDependingOnInterface which depends on MyInterface interface. I'd like the struct Implementation to be injected into bar when StructDependingOnInterface is instantiated.
I have tried doing this with facebookgo/inject library, but it seems like it doesn't work with interfaces. s.bar is always nil.
package main
import (
"fmt"
"log"
"github.com/facebookgo/inject"
)
type MyInterface interface {
Foo()
}
type Implementation struct{}
func (imp *Implementation) Foo() {
fmt.Println("Hello")
}
type StructDependingOnInterface struct {
bar MyInterface `inject:""`
}
func main() {
var g inject.Graph
err := g.Provide(
&inject.Object{Value: &Implementation{}},
)
if err != nil {
log.Fatal(err)
}
g.Populate()
s := &StructDependingOnInterface{}
s.bar.Foo()
}
Does the language go allows what I'm trying to do ?
Is there an alternative of facebookgo/inject that would fits my need ?
facebookgo/inject should be able to handle interfaces, look at the example in the link and how they use http.RoundTripper and http.DefaultTransport.
First you need to export bar, i.e. changing it to Bar. Then you also need to pass s to Provide for g.Populate to then be able to set Bar.
type StructDependingOnInterface struct {
Bar MyInterface `inject:""`
}
func main() {
var g inject.Graph
s := &StructDependingOnInterface{}
err := g.Provide(
&inject.Object{Value: s},
&inject.Object{Value: &Implementation{}},
)
if err != nil {
log.Fatal(err)
}
if err := g.Populate(); err != nil {
log.Fatal(err)
}
s.bar.Foo()
}
hiboot is an cli/web framework that support dependency injection written in Go.
Code: https://github.com/hidevopsio/hiboot
Hiboot Overview
Web MVC (Model-View-Controller).
Auto Configuration, pre-create instance with properties configs for dependency injection.
Dependency injection with struct tag name inject:"" or Constructor func.
Sample Code:
package main
import (
"github.com/hidevopsio/hiboot/pkg/app/web"
"github.com/hidevopsio/hiboot/pkg/model"
"github.com/hidevopsio/hiboot/pkg/starter/jwt"
"time"
)
// This example shows that token is injected through constructor,
// once you imported "github.com/hidevopsio/hiboot/pkg/starter/jwt",
// token jwt.Token will be injectable.
func main() {
// create new web application and run it
web.NewApplication().Run()
}
// PATH: /login
type loginController struct {
web.Controller
token jwt.Token
}
type userRequest struct {
// embedded field model.RequestBody mark that userRequest is request body
model.RequestBody
Username string `json:"username" validate:"required"`
Password string `json:"password" validate:"required"`
}
func init() {
// Register Rest Controller through constructor newLoginController
web.RestController(newLoginController)
}
// inject token through the argument token jwt.Token on constructor newLoginController, you may wonder why is token come from
// well, it provided by hiboot starter jwt, see above hiboot link for more details
func newLoginController(token jwt.Token) *loginController {
return &loginController{
token: token,
}
}
// Post /
// The first word of method is the http method POST, the rest is the context mapping
func (c *loginController) Post(request *userRequest) (response model.Response, err error) {
jwtToken, _ := c.token.Generate(jwt.Map{
"username": request.Username,
"password": request.Password,
}, 30, time.Minute)
response = new(model.BaseResponse)
response.SetData(jwtToken)
return
}

Creating a variable of type from interface in golang

I am trying to create validator/binder middleware in go using gin framework.
This is the model
type LoginForm struct{
Email string `json:"email" form:"email" binding:"email,required"`
Password string `json:"password" form:"password" binding:"required"`
}
Router
router.POST("/login",middlewares.Validator(LoginForm{}) ,controllers.Login)
Middleware
func Validator(v interface{}) gin.HandlerFunc{
return func(c *gin.Context){
a := reflect.New(reflect.TypeOf(v))
err:=c.Bind(&a)
if(err!=nil){
respondWithError(401, "Login Error", c)
return
}
c.Set("LoginForm",a)
c.Next()
}
}
I am very new to golang. I understand the problem is with the binding to the wrong variable.
Is there any other way of solving this?
Clarify my comment,
Instead of having the signature func Validator(v interface{}) gin.HandlerFunc for the MW, use func Validator(f Viewfactory) gin.HandlerFunc
Where ViewFactory if a function type such as type ViewFactory func() interface{}
the MW can be changed so
type ViewFactory func() interface{}
func Validator(f ViewFactory) gin.HandlerFunc{
return func(c *gin.Context){
a := f()
err:=c.Bind(a) // I don t think you need to send by ref here, to check by yourself
if(err!=nil){
respondWithError(401, "Login Error", c)
return
}
c.Set("LoginForm",a)
c.Next()
}
}
You can write the router like this
type LoginForm struct{
Email string `json:"email" form:"email" binding:"email,required"`
Password string `json:"password" form:"password" binding:"required"`
}
func NewLoginForm() interface{} {
return &LoginForm{}
}
router.POST("/login",middlewares.Validator(NewLoginForm) ,controllers.Login)
Going further, i think you may have to find out about this later, once you have an interface{} value, you can make it back a LoginForm like this v := some.(*LoginForm).
Or like this for more security
if v, ok := some.(*LoginForm); ok {
// v is a *LoginForm
}
See golang type assertions for more in depth information.

Resources