techschool / simplebank

Backend master class: build a simple bank service in Go
MIT License
5.17k stars 939 forks source link

Is it possible to bind `uri` and `json` for the same struct? #94

Open chengr4 opened 1 year ago

chengr4 commented 1 year ago

Eg. for updateAccount API, How to bind uri and json for the same struct?

type updateAccountRequest struct {
    ID int64 `uri:"id" binding:"required,min=1"`
    Currency string `json:"currency" binding:"required,currency"`
}

func (server *Server) updateArticle(ctx *gin.Context) {
    var req updateAccountRequest
    if err := ctx.ShouldBindUri(&req); err != nil {
        // send 400 Bad Request to the client
        ctx.JSON(http.StatusBadRequest, errorResponse(err))
        return
    }

    if err := ctx.ShouldBindJSON(&req); err != nil {
        // send 400 Bad Request to the client
        ctx.JSON(http.StatusBadRequest, errorResponse(err))
        return
    }
}

the code above does not work.

Does anyone know? Thanks in advance

chengr4 commented 1 year ago

I use result here: https://github.com/gin-gonic/gin/issues/1824#issuecomment-475968345 but I wonder whether there is a better solution

TaylorDurden commented 2 months ago

I use result here: gin-gonic/gin#1824 (comment) but I wonder whether there is a better solution

type updateAccountRequest struct {
    ID      int64 `uri:"id" binding:"required,min=1"`
    Balance int64 `json:"balance"`
}

func (server *Server) updateAccountHandler(ctx *gin.Context) {
    var req updateAccountRequest
    if err := ctx.ShouldBindUri(&req); err != nil {
        ctx.JSON(http.StatusBadRequest, errorResponse(err))
        return
    }

    if err := ctx.ShouldBindJSON(&req); err != nil {
        ctx.JSON(http.StatusBadRequest, errorResponse(err))
        return
    }
    arg := db.UpdateAccountParams{
        ID:      req.ID,
        Balance: req.Balance,
    }
    updatedAccount, err := server.store.UpdateAccount(ctx, arg)
    if err != nil {
        if err == sql.ErrNoRows {
            ctx.JSON(http.StatusNotFound, errorResponse(err))
            return
        }
        ctx.JSON(http.StatusInternalServerError, errorResponse(err))
        return
    }

    ctx.JSON(http.StatusOK, updatedAccount)
}
chengr4 commented 1 month ago

@TaylorDurden thanks for help. However, if I recall correctly, this may not work.

TaylorDurden commented 1 month ago

@TaylorDurden thanks for help. However, if I recall correctly, this may not work.

@TaylorDurden thanks for help. However, if I recall correctly, this may not work.

https://github.com/TaylorDurden/go-simple-bank/pull/4

You can try it in my PR branch. Maybe the new version gin resolved this.

TaylorDurden commented 1 month ago

@chengr4 I guess that maybe you were testing a 400 Bad Request to the API client without currency field. If you mark the currency field not required, this field won't be validated for 1 struct.

// this required binding will fail ctx.ShouldBindUri validation
Currency string `json:"currency" binding:"required,currency"` 

// this code will validate the struct fileds, if you did not pass the Currency field
ctx.ShouldBindUri(&req)  

FYI: https://github.com/gin-gonic/gin/blob/cc4e11438cd6c0bcc632fe3492d3817dfa21c337/context.go#L763 https://github.com/gin-gonic/gin/blob/cc4e11438cd6c0bcc632fe3492d3817dfa21c337/binding/uri.go#L17

chengr4 commented 1 month ago

@TaylorDurden But I usually want to validate it

TaylorDurden commented 1 month ago

@TaylorDurden But I usually want to validate it

Then you should use another struct for this param...since it is from request body and use ShouldBindJSON to parse & validate it. Although it is not a clean solution but it works as you metioned above...