英文:
More elegant way of validate body in go-gin
问题
有没有更优雅的方法来使用go-gin
验证json body
和route id
?以下是代码的翻译:
package controllers
import (
"giin/inputs"
"net/http"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
func GetAccount(context *gin.Context) {
// 验证`accountId`是否为有效的`uuid`
_, err := uuid.Parse(context.Param("accountId"))
if err != nil {
context.JSON(http.StatusBadRequest, gin.H{"message": err.Error()})
return
}
// 这里是一些逻辑...
context.JSON(http.StatusOK, gin.H{"message": "收到账户"})
}
func AddAccount(context *gin.Context) {
// 验证`body`是否为有效的`inputs.Account`
var input inputs.Account
if error := context.ShouldBindJSON(&input); error != nil {
context.JSON(http.StatusBadRequest, error.Error())
return
}
// 这里是一些逻辑...
context.JSON(http.StatusOK, gin.H{"message": "添加账户"})
}
我创建了一个中间件,能够检测是否传递了accountId
,如果是,则验证它并返回错误请求,如果accountId
不是uuid格式。但是对于body
,我无法做到同样的事情,因为AccountBodyMiddleware
尝试验证每个请求,有人能帮我解决这个问题吗?
而且,如果我能够验证任何类型的body
,而不是为每个json body
创建新的中间件,那将非常好。
package main
import (
"giin/controllers"
"giin/inputs"
"net/http"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
func AccountIdMiddleware(c *gin.Context) {
id := c.Param("accountId")
if id == "" {
c.Next()
return
}
if _, err := uuid.Parse(id); err != nil {
c.JSON(http.StatusBadRequest, "uuid无效")
c.Abort()
return
}
}
func AccountBodyMiddleware(c *gin.Context) {
var input inputs.Account
if error := c.ShouldBindJSON(&input); error != nil {
c.JSON(http.StatusBadRequest, "body无效")
c.Abort()
return
}
c.Next()
}
func main() {
r := gin.Default()
r.Use(AccountIdMiddleware)
r.Use(AccountBodyMiddleware)
r.GET("/account/:accountId", controllers.GetAccount)
r.POST("/account", controllers.AddAccount)
r.Run(":5000")
}
希望对你有所帮助!
英文:
Is there a more elegant way to validate json body
and route id
using go-gin
?
package controllers
import (
"giin/inputs"
"net/http"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
func GetAccount(context *gin.Context) {
// validate if `accountId` is valid `uuid``
_, err := uuid.Parse(context.Param("accountId"))
if err != nil {
context.JSON(http.StatusBadRequest, gin.H{"message": err.Error()})
return
}
// some logic here...
context.JSON(http.StatusOK, gin.H{"message": "account received"})
}
func AddAccount(context *gin.Context) {
// validate if `body` is valid `inputs.Account`
var input inputs.Account
if error := context.ShouldBindJSON(&input); error != nil {
context.JSON(http.StatusBadRequest, error.Error())
return
}
// some logic here...
context.JSON(http.StatusOK, gin.H{"message": "account added"})
}
I created middleware which is able to detect if accountId
was passed and if yes validate it and return bad request if accountId
was not in uuid format but I couldn't do the same with the body because AccountBodyMiddleware
tries to validate every request, could someone help me with this?
And also it would be nice if I could validate any type of body instead creating new middleware for each json body
package main
import (
"giin/controllers"
"giin/inputs"
"net/http"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
func AccountIdMiddleware(c *gin.Context) {
id := c.Param("accountId")
if id == "" {
c.Next()
return
}
if _, err := uuid.Parse(id); err != nil {
c.JSON(http.StatusBadRequest, "uuid not valid")
c.Abort()
return
}
}
func AccountBodyMiddleware(c *gin.Context) {
var input inputs.Account
if error := c.ShouldBindJSON(&input); error != nil {
c.JSON(http.StatusBadRequest, "body is not valid")
c.Abort()
return
}
c.Next()
}
func main() {
r := gin.Default()
r.Use(AccountIdMiddleware)
r.Use(AccountBodyMiddleware)
r.GET("/account/:accountId", controllers.GetAccount)
r.POST("/account", controllers.AddAccount)
r.Run(":5000")
}
答案1
得分: 2
使用中间件在这里肯定不是正确的方法,你的直觉是正确的!受到FastAPI的启发,我通常为每个请求/响应创建模型。然后,你可以将这些模型绑定为查询、路径或正文模型。这是一个查询模型绑定的示例(只是为了向你展示你可以将其用于更多类型的请求,而不仅仅是JSON POST请求):
type User struct {
UserId string `form:"user_id"`
Name string `form:"name"`
}
func (user *User) Validate() errors.RestError {
if _, err := uuid.Parse(id); err != nil {
return errors.BadRequestError("user_id不是有效的UUID")
}
return nil
}
其中,errors只是一个你可以在本地定义的包,以便可以直接返回验证错误,如下所示:
func GetUser(c *gin.Context) {
// 绑定查询模型
var q User
if err := c.ShouldBindQuery(&q); err != nil {
restError := errors.BadRequestError(err.Error())
c.JSON(restError.Status, restError)
return
}
// 验证请求
if err := q.Validate(); err != nil {
c.JSON(err.Status, err)
return
}
// 业务逻辑在这里
}
额外奖励:通过这种方式,你还可以组合结构体并从高层调用内部验证函数。我认为这就是你试图通过使用中间件来实现的(组合验证):
type UserId struct {
Id string
}
func (userid *UserId) Validate() errors.RestError {
if _, err := uuid.Parse(id); err != nil {
return errors.BadRequestError("user_id不是有效的UUID")
}
return nil
}
type User struct {
UserId
Name string
}
func (user *User) Validate() errors.RestError {
if err := user.UserId.Validate(); err != nil {
return err
}
// 进行其他验证
return nil
}
额外奖励:如果你有兴趣,可以在这里阅读更多关于后端路由设计和基于模型的验证的内容Softgrade - 后端路由设计深度指南。
供参考,这是一个错误结构的示例:
type RestError struct {
Message string `json:"message"`
Status int `json:"status"`
Error string `json:"error"`
}
func BadRequestError(message string) *RestError {
return &RestError{
Message: message,
Status: http.StatusBadRequest,
Error: "无效的请求",
}
}
英文:
Using middlewares is certainly not the way to go here, your hunch is correct! Using FastAPI as inspiration, I usually create models for every request/response that I have. You can then bind these models as query, path, or body models. An example of query model binding (just to show you that you can use this to more than just json post requests):
type User struct {
UserId string `form:"user_id"`
Name string `form:"name"`
}
func (user *User) Validate() errors.RestError {
if _, err := uuid.Parse(id); err != nil {
return errors.BadRequestError("user_id not a valid uuid")
}
return nil
}
Where errors is just a package you can define locally, so that can return validation errors directly in the following way:
func GetUser(c *gin.Context) {
// Bind query model
var q User
if err := c.ShouldBindQuery(&q); err != nil {
restError := errors.BadRequestError(err.Error())
c.JSON(restError.Status, restError)
return
}
// Validate request
if err := q.Validate(); err != nil {
c.JSON(err.Status, err)
return
}
// Business logic goes here
}
Bonus: In this way, you can also compose structs and call internal validation functions from a high level. I think this is what you were trying to accomplish by using middlewares (composing validation):
type UserId struct {
Id string
}
func (userid *UserId) Validate() errors.RestError {
if _, err := uuid.Parse(id); err != nil {
return errors.BadRequestError("user_id not a valid uuid")
}
return nil
}
type User struct {
UserId
Name string
}
func (user *User) Validate() errors.RestError {
if err := user.UserId.Validate(); err != nil {
return err
}
// Do some other validation
return nil
}
Extra bonus: read more about backend route design and model-based validation here if you're interested Softgrade - In Depth Guide to Backend Route Design
For reference, here is an example errors struct:
type RestError struct {
Message string `json:"message"`
Status int `json:"status"`
Error string `json:"error"`
}
func BadRequestError(message string) *RestError {
return &RestError{
Message: message,
Status: http.StatusBadRequest,
Error: "Invalid Request",
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论