Подтвердить что ты не робот

Идиоматический способ проверки структур в Go?

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

type Event struct {
    Id     int
    UserId int
    Start  time.Time
    End    time.Time
    Title  string
    Notes  string
}

func (e Event) IsValid() error {
    if e.Id <= 0 {
        return errors.New("Id must be greater than 0")
    }
    if e.UserId <= 0 {
        return errors.New("UserId must be greater than 0")
    }
    if e.End <= e.Start {
        return errors.New("End must be after Start")
    }
    if e.Start < time.Now() {
        return errors.New("Cannot create events in the past")
    }
    if e.Title == "" {
        return errors.New("Title cannot be empty")
    }
    return nil
}

Является ли это идиоматическим способом проверки значений полей в структуре? Это выглядит громоздким.

4b9b3361

Ответ 1

Я не вижу другого способа сделать это быстро. Но я нашел пакет go, который поможет вам в этом: https://github.com/go-validator/validator

В файле README приведен пример:

type NewUserRequest struct {
    Username string `validator:"min=3,max=40,regexp=^[a-zA-Z]$"`
    Name string     `validator:"nonzero"`
    Age int         `validator:"min=21"`
    Password string `validator:"min=8"`
}

nur := NewUserRequest{Username: "something", Age: 20}
if valid, errs := validator.Validate(nur); !valid {
    // values not valid, deal with errors here
}

Ответ 2

Таким образом, вы в конечном итоге написали много дубликата кода для каждой вашей модели.

Использование библиотеки с тегами имеет свои плюсы и минусы. Иногда легко начинать, но по дороге вы сталкиваетесь с ограничениями библиотеки.

Одним из возможных подходов является создание "Validator", и его единственной обязанностью является отслеживание возможных ошибок внутри объекта.

Очень приблизительная заглушка этой идеи:

http://play.golang.org/p/buBUzk5z6I

package main

import (
    "fmt"
    "time"
)

type Event struct {
    Id     int
    UserId int
    Start  time.Time
    End    time.Time
    Title  string
    Notes  string
}

type Validator struct {
    err error
}

func (v *Validator) MustBeGreaterThan(high, value int) bool {
    if v.err != nil {
        return false
    }
    if value <= high {
        v.err = fmt.Errorf("Must be Greater than %d", high)
        return false
    }
    return true
}

func (v *Validator) MustBeBefore(high, value time.Time) bool {
    if v.err != nil {
        return false
    }
    if value.After(high) {
        v.err = fmt.Errorf("Must be Before than %v", high)
        return false
    }
    return true
}

func (v *Validator) MustBeNotEmpty(value string) bool {
    if v.err != nil {
        return false
    }
    if value == "" {
        v.err = fmt.Errorf("Must not be Empty")
        return false
    }
    return true
}

func (v *Validator) IsValid() bool {
    return v.err != nil
}

func (v *Validator) Error() string {
    return v.err.Error()
}

func main() {
    v := new(Validator)
    e := new(Event)
    v.MustBeGreaterThan(e.Id, 0)
    v.MustBeGreaterThan(e.UserId, 0)
    v.MustBeBefore(e.End, e.Start)
    v.MustBeNotEmpty(e.Title)
    if !v.IsValid() {
        fmt.Println(v)
    } else {
    fmt.Println("Valid")
    }
}
package main

import (
    "fmt"
    "time"
)

type Event struct {
    Id     int
    UserId int
    Start  time.Time
    End    time.Time
    Title  string
    Notes  string
}

type Validator struct {
    err error
}

func (v *Validator) MustBeGreaterThan(high, value int) bool {
    if v.err != nil {
        return false
    }
    if value <= high {
        v.err = fmt.Errorf("Must be Greater than %d", high)
        return false
    }
    return true
}

func (v *Validator) MustBeBefore(high, value time.Time) bool {
    if v.err != nil {
        return false
    }
    if value.After(high) {
        v.err = fmt.Errorf("Must be Before than %v", high)
        return false
    }
    return true
}

func (v *Validator) MustBeNotEmpty(value string) bool {
    if v.err != nil {
        return false
    }
    if value == "" {
        v.err = fmt.Errorf("Must not be Empty")
        return false
    }
    return true
}

func (v *Validator) IsValid() bool {
    return v.err != nil
}

func (v *Validator) Error() string {
    return v.err.Error()
}

func main() {
    v := new(Validator)
    e := new(Event)
    v.MustBeGreaterThan(e.Id, 0)
    v.MustBeGreaterThan(e.UserId, 0)
    v.MustBeBefore(e.End, e.Start)
    v.MustBeNotEmpty(e.Title)
    if !v.IsValid() {
        fmt.Println(v)
    } else {
    fmt.Println("Valid")
    }
}

Затем вы можете создать свой метод Validate и использовать тот же код:

func (e *Event) IsValid() error {
        v := new(Validator)
    v.MustBeGreaterThan(e.Id, 0)
    v.MustBeGreaterThan(e.UserId, 0)
    v.MustBeBefore(e.End, e.Start)
    v.MustBeNotEmpty(e.Title)
    return v.IsValid()
}

Ответ 3

Чтобы помочь кому-либо еще, кто может искать другую библиотеку проверки, я создал следующее https://github.com/bluesuncorp/validator

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

  • Возврат всех ошибок проверки
  • несколько проверок в поле
  • проверка поперечного поля ex. Начало > Дата окончания

Вдохновленный несколькими другими проектами, включая принятый ответ go-validator/validator

Ответ 4

Я бы написал явный код, а не использовал библиотеку проверки. Преимущество написания собственного кода заключается в том, что вы не добавляете дополнительную зависимость, вам не нужно изучать DSL, и вы можете проверить свойства ваших структур, которые зависят от нескольких полей (например, start < конец).

Чтобы вырезать шаблон, я мог бы извлечь функцию, которая добавляет сообщение об ошибке в кусок ошибок в случае, если инвариант является ложным.

func check(ea *[]string, c bool, errMsg string, ...args) {
    if !c { *ea = append(*ea, fmt.Sprintf(errMsg, ...args)) }
}

func (e *Event) Validate() error {
    var ea []string
    check(&ea, e.ID >= 0, "want positive ID, got %d", e.ID)
    check(&ea, e.Start < e.End, "want start < end, got %s >= %s", e.Start, e.End)
    ...
    if len(ea) > 0 {
        return errors.New(strings.Join(ea, ", "))
    }
    return nil
 }

Это возвращает все пути, в которых структура не выполняет проверку, а не только первую, что может быть или не быть тем, что вы хотите.

Ответ 5

Возможно, вы можете попробовать validating. С помощью этой библиотеки вы можете проверить свою структуру следующим образом:

package main

import (
    "fmt"
    "time"

    v "github.com/RussellLuo/validating"
)

type Event struct {
    Id     int
    UserId int
    Start  time.Time
    End    time.Time
    Title  string
    Notes  string
}

func (e *Event) Schema() v.Schema {
    return v.Schema{
        v.F("id", &e.Id):          v.Gt(0),
        v.F("user_id", &e.UserId): v.Gt(0),
        v.F("start", &e.Start):    v.Gte(time.Now()),
        v.F("end", &e.End):        v.Gt(e.Start),
        v.F("title", &e.Title):    v.Nonzero(),
        v.F("notes", &e.Notes):    v.Nonzero(),
    }
}

func main() {
    e := Event{}
    err := v.Validate(e.Schema())
    fmt.Printf("err: %+v\n", err)
}

Ответ 6

Другой подход, который не нуждается в отражении и возвращает первую ошибку, использует нечто вроде this:

type Event struct {
    Id     int
    UserId int
    Start  time.Time
    End    time.Time
    Title  string
    Notes  string
}

func (e *Event) Validate() error {
    return Check(
        Cf(e.Id <= 0, "Expected ID <= 0, got %d.", e.Id),
        Cf(e.Start.UnixNano() > e.End.UnixNano(), "Expected start < end, got %s >= %s.", e.Start, e.End),
    )
}

type C struct {
    Check bool
    Error error
}

func Cf(chk bool, errmsg string, params ...interface{}) C {
    return C{chk, fmt.Errorf(errmsg, params...)}
}

func Check(args ...C) error {
    for _, c := range args {
        if !c.Check {
            return c.Error
        }
    }
    return nil
}

func main() {
    a := Event{Id: 1, Start: time.Now()}
    b := Event{Id: -1}
    fmt.Println(a.Validate(), b.Validate())
}

Ответ 7

Метод, который вы описываете, является, безусловно, самым прямым способом сделать это.

Вы можете использовать отражение с тегами поля структуры для автоматической проверки. Но для этого потребуется написать библиотеку, которая сделает это за вас. Положительным моментом является то, что после того, как вы написали библиотеку валидации, вы можете повторно использовать ее с любой структурой.

Пример использования этого кода:

type Person struct {
    Name string `minlength:"3" maxlength:"20"`
    Age  int    `min:"18" max:"80"`
}

Вы должны создать экземпляр этого типа и передать его в свой код проверки. Он использовал бы правила в тегах полей для проверки значений полей.

Есть, вероятно, несколько библиотек, которые делают это для вас, но я не уверен, насколько они работают или если они все еще поддерживаются.

Ответ 8

Я думаю, что это лучший способ!

import (
    "fmt"

    "github.com/bytedance/go-tagexpr/validator"
)

func Example() {
    var vd = validator.New("vd")

    type InfoRequest struct {
        Name string 'vd:"($!='Alice'||(Age)$==18) && regexp('\\w')"'
        Age  int    'vd:"$>0"'
    }
    info := &InfoRequest{Name: "Alice", Age: 18}
    fmt.Println(vd.Validate(info) == nil)
}

https://github.com/bytedance/go-tagexpr/tree/master/validator