Вернуть пользовательское сообщение об ошибке из проверки тега структуры

avatar
John
22 ноября 2021 в 17:15
2120
1
3

Я использую Go 1.17 с Gin и хочу реализовать проверку структуры перед отправкой данных в базу данных. Я взял пример из документации Gin.

В структуре мы можем объявить разные теги для проверки поля следующим образом:

type User struct {
    FirstName      string `json:"first_name" binding:"required"`
    LastName       string `json:"last_name" binding:"required"`
    Age            uint8  `json:"age" binding:"gte=0,lte=130"`
    Email          string `json:"email" binding:"required,email"`
    FavouriteColor string `json:"favourite_color" binding:"iscolor"`
}

И в обработчике я могу получить ошибку следующим образом:

var u User
if err := c.ShouldBindWith(&u, binding.Query); err == nil {
    c.JSON(http.StatusOK, gin.H{"message": "Good Job"})
} else {
    c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
}

Сообщение об ошибке будет таким:

{
    "error": "Key: 'User.FirstName' Error:Field validation for 'FirstName' failed on the 'required' tag\nKey: 'User.LastName' Error:Field validation for 'LastName' failed on the 'required' tag\nKey: 'User.Email' Error:Field validation for 'Email' failed on the 'required' tag\nKey: 'User.FavouriteColor' Error:Field validation for 'FavouriteColor' failed on the 'iscolor' tag"
}

Сообщения об ошибках слишком многословны. Как можно вернуть пользователю лучшую ошибку? Я хотел бы смоделировать ответ json, например:

{
    "errors": [
        "first_name": "This field is required",
        "last_name": "This field is required",
        "age": "This field is required",
        "email": "Invalid email"
    ]
}
Источник

Ответы (1)

avatar
blackgreen
22 ноября 2021 в 20:45
2

Gin gonic использует пакет github.com/go-playground/validator/v10 для проверки привязки. Если проверка не пройдена, возвращается ошибка validator.ValidationErrors.

.

Это не упоминается явно, но здесь в разделе Привязка и проверка модели указано:

Gin использует go-playground/validator/v10 для проверки. Ознакомьтесь с полной документацией по использованию тегов здесь.

Это ссылка на документацию go-playground/validator/v10, где вы найдете абзац Ошибка типа возврата функций проверки.

Вы можете использовать стандартный пакет errors, чтобы проверить, является ли ошибка ошибкой, распаковать его и получить доступ к отдельным полям, а именно validator.FieldError. Исходя из этого, вы можете создать любое сообщение об ошибке, какое захотите.

Для такой модели ошибки:

type ApiError struct {
    Field string
    Msg   string
}

Вы можете сделать это:

    var u User
    err := c.BindQuery(&u);
    if err != nil {
        var ve validator.ValidationErrors
        if errors.As(err, &ve) {
            out := make([]ApiError, len(ve))
            for i, fe := range ve {
                out[i] = ApiError{fe.Field(), msgForTag(fe.Tag())}
            }
            c.JSON(http.StatusBadRequest, gin.H{"errors": out})
        }
        return
    }

с вспомогательной функцией для вывода пользовательского сообщения об ошибке для ваших правил проверки:

func msgForTag(tag string) string {
    switch tag {
    case "required":
        return "This field is required"
    case "email":
        return "Invalid email"
    }
    return ""
}

В моем тесте это выводит:

{
    "errors": [
        {
            "Field": "Number",
            "Msg": "This field is required"
        }
    ]
}

PS: Чтобы получить вывод json с динамическими ключами, вы можете использовать map[string]string вместо фиксированной модели структуры.

John
23 ноября 2021 в 13:30
0

Есть ли способ получить имя тега структуры json в значении поля? Например, как вы можете видеть в поле FirstName, я назвал свой json-тег first_name. Я проверил документ pkg.go.dev/github.com/go-playground/validator/v10#FieldError и метод fe.Field() должен работать, но на самом деле у меня такое же поведение, как у fe.StructField(). не знаю почему

blackgreen
23 ноября 2021 в 14:45
1

@John, чтобы получить разные результаты, используйте RegisterTagNameFunc

John
23 ноября 2021 в 15:07
1

Оууу, ты снова спасаешь мой день. Я думал, что это автоматически. Я помещу это в это условие: if v, ok := binding.Validator.Engine().(*validator.Validate); ok {