1

Pitch: govalid: validação de structs em Go sem transformar tags em uma mini linguagem

Recentemente comecei a desenvolver o govalid, uma biblioteca de validação para Go que nasceu principalmente de uma preferência pessoal que fui criando enquanto estudava outras soluções: eu não queria precisar aprender mais um pequeno "dialeto" dentro das tags das structs.

Em muitas bibliotecas, é comum encontrar algo parecido com:

type User struct {
    Name  string `validate:"required,min=3"`
    Email string `validate:"required,email"`
    Age   uint   `validate:"gte=18,lte=130"`
}

Não há necessariamente algo errado com essa abordagem. Ela é compacta, bastante utilizada e existem bibliotecas maduras seguindo esse modelo.

O que começou a me incomodar é que, conforme as validações crescem, também cresce a quantidade de regras e sintaxes que preciso conhecer dentro de uma string.

Minha ideia com o govalid foi experimentar outra abordagem: manter as validações como código Go explícito.

Por exemplo:

err := govalid.New().Validate(
    user,
    govalid.Field(
        "Name",
        govalid.Required(),
        govalid.StringMinLength(3),
    ),
    govalid.Field(
        "Age",
        govalid.IntBetween(18, 130),
    ),
    govalid.Field(
        "Profile.Email",
        govalid.StringEmail(),
    ),
)

As regras são funções Go e podem ser compostas:

govalid.Field(
    "Scores",
    govalid.Optional(
        govalid.CollectionEach(
            govalid.IntBetween(0, 100),
        ),
    ),
)

Além das regras prontas, também é possível escrever uma Rule normalmente:

isSlug := govalid.Rule(func(ctx govalid.RuleContext) error {
    value, ok := ctx.ValueAny().(string)

    if !ok || value == "" {
        return errors.New("should be a slug")
    }

    return nil
})

Atualmente existem regras para strings, inteiros com e sem sinal, floats, booleanos, bytes, maps e collections, além de validações condicionais.

Outro ponto que tentei manter foi a composição. Por exemplo, uma regra de inteiro pode ser utilizada diretamente em um campo ou aplicada aos elementos de uma collection.

Por que não tags?

A intenção não é dizer que validação por tags é ruim.

O objetivo é oferecer uma alternativa para quem, assim como eu, prefere escrever:

govalid.IntBetween(18, 130)

em vez de precisar lembrar qual seria a representação equivalente dentro de uma tag.

Isso tem um custo: as validações ficam mais verbosas.

Por outro lado, elas ficam explícitas no código, navegáveis pela IDE e podem ser compostas utilizando Go normalmente.

E para projetos maiores?

Essa é uma parte em que ainda tenho dúvidas.

Hoje vejo o projeto principalmente como uma alternativa para aplicações pequenas e médias. A biblioteca já possui testes com estruturas e payloads relativamente grandes, mas isso é diferente de afirmar que ela está preparada para todos os requisitos encontrados em sistemas grandes.

Inclusive, esse é um dos motivos de estar compartilhando o projeto.

Quero entender quais problemas essa abordagem começaria a encontrar conforme a aplicação cresce e quais casos de uso ainda não estou considerando.

GitHub:
https://github.com/rickferrdev/govalid

Quem trabalha com validação em Go: vocês preferem manter essas regras explícitas em código ou acham que a concisão das tags compensa ter essa sintaxe adicional?

Carregando publicação patrocinada...