07 Validação

O VRaptor3 suporta dois estilos de validação: clássico e fluente. A porta de entrada para ambos os estilos é a interface Validator. Para que seu recurso tenha acesso ao Validator, basta recebê-lo no seu construtor:

import br.com.caelum.vraptor.Validator;
...

@Resource
class FuncionarioController {
    private Validator validator;

    public FuncionarioController(Validator validator) {
        this.validator = validator;
    }
}

Estilo clássico

A forma clássica é semelhante à forma como as validações eram feitas no VRaptor2. Dentro da sua lógica de negócios, basta fazer a verificação que deseja e caso haja um erro de validação, adicionar esse erro na lista de erros de validação. Por exemplo, para validar que o nome do funcionario deve ser Fulano, faça:

public void adiciona(Funcionario funcionario) {
    if (!funcionario.getNome().equals("Fulano")) {
        validator.add(new ValidationMessage("nome.invalido", "erro"));
    }

    validator.onErrorUsePageOf(FuncionarioController.class).formulario();

    dao.adiciona(funcionario);
}

Ao chamar o validator.onErrorUse, se existirem erros de validação, o VRaptor para a execução e redireciona à página que você indicou. O redirecionamento funciona da mesma forma que o result.use(..).

Estilo fluente

No estilo fluente, a ideia é que o código para fazer a validação seja algo muito parecido com a linguagem natural. Por exemplo, caso queiramos obrigar que seja informado o nome do funcionário:

public adiciona(Funcionario funcionario) {
    validator.checking(new Validations() { {
        that(!funcionario.getNome().isEmpty(), "erro", "nome.nao.informado");
    } });

    validator.onErrorUsePageOf(FuncionarioController.class).formulario();

    dao.adiciona(funcionario);
}

Você pode ler esse código como: "Validador, cheque as minhas validações. A primeira validação é que o nome do funcionário não pode ser vazio". Bem mais próximo a linguagem natural. Assim sendo, caso o nome do funcionário seja vazio, ele vai ser redirecionado novamente para a lógica "formulario", para que o funcionário seja adicionado novamente. Além disso, ele devolve para o formulario a mensagem de erro que aconteceu na validação. Muitas vezes, algumas validações só precisam acontecer se uma outra deu certo, por exemplo, eu só vou checar a idade do usuário se o usuário não for null. O método that retorna um boolean dizendo se o que foi passado pra ele é válido ou não:

validator.checking(new Validations() { {
    if (that(usuario != null, "usuario", "usuario.nulo")) {
        that(usuario.getIdade() >= 18, "usuario.idade", "usuario.menor.de.idade");
    }
} });

Desse jeito a segunda validação só acontece se a primeira não falhou.

Validação com parâmetros nas mensagens

Você pode colocar parâmetros nas suas mensagens internacionalizadas:

maior.que = {0} deveria ser maior que {1}

E usá-los no seu código de validação:

validator.checking(new Validations() { {
    that(usuario.getIdade() >= 18, "usuario.idade", "maior.que", "Idade", 18);
    // Idade deveria ser maior que 18
} });

Você pode também internacionalizar os parâmetros, usando o método i18n:

usuario.idade = Idade do usuário
validator.checking(new Validations() { {
    that(usuario.getIdade() >= 18, "usuario.idade", "maior.que", i18n("usuario.idade"), 18);
    // Idade do usuário deveria ser maior que 18
} });

Validação usando matchers do Hamcrest

Você pode também usar matchers do Hamcrest para deixar a validação mais legível, e ganhar a vantagem da composição de matchers e da criação de novos matchers que o Hamcrest lhe oferece:

public admin(Funcionario funcionario) {
    validator.checking(new Validations() { {
        that(funcionario.getRoles(), hasItem("ADMIN"), "admin", "funcionario.nao.eh.admin");
    } });

    validator.onErrorUsePageOf(LoginController.class).login();

    dao.adiciona(funcionario);
}

Bean Validation

O VRaptor também suporta integração com o Bean Validation e o Hibernate Validator. Para usar as validações basta adicionar no seu classpath qualquer implementação do Bean Validation. No exemplo anterior, para validar o objeto Funcionario basta uma adicionar uma linha de código:

public adiciona(Funcionario funcionario) {
    // Validação do Funcionario com o Bean Validator ou Hibernate Validator
    validator.validate(funcionario);

    validator.checking(new Validations() { {
        that(!funcionario.getNome().isEmpty(), "erro", "nome.nao.informado");
    } });

    validator.onErrorUsePageOf(FuncionarioController.class).formulario();

    dao.adiciona(funcionario);
}

A partir da versão 3.5 o VRaptor possui suporte para validações nos métodos. Para isso é necessário ter no classpath alguma implementação do Bean Validator 1.1. Desta forma qualquer parâmetro pode ser validado usando anotações conforme o exemplo abaixo:

public adiciona(@NotNull String name, @Future dueDate) {
    ...
}

Para onde redirecionar no caso de erro?

Outro ponto importante que deve ser levado em consideração no momento de fazer validações é o redirecionamento quando ocorrer um erro. Como enviamos o usuário para outro recurso com o VRaptor3, caso haja erro na validação? Simples, apenas diga no seu código que quando correr um erro, é para o usuário ser enviado para algum recurso. Como no exemplo:

public adiciona(Funcionario funcionario) {
    // Validação na forma fluente
    validator.checking(new Validations() { {
        that("erro","nome.nao.informado", !funcionario.getNome().isEmpty());
    } });

    // Validação na forma clássica
    if (!funcionario.getNome().equals("Fulano")) {
        validator.add(new ValidationMessage("erro","nomeInvalido"));
    }

    validator.onErrorUse(page()).of(FuncionarioController.class).formulario();

    dao.adiciona(funcionario);
}

Note que se sua lógica adiciona algum erro de validação você precisa dizer pra onde o VRaptor deve ir. O validator.onErrorUse funciona do mesmo jeito que o result.use: você pode usar qualquer view da classe Results.

Atalhos no Validator

Alguns redirecionamentos são bastante utilizados, então foram criados atalhos para eles. Os atalhos disponíveis são:

Além disso, se o redirecionamento é para um método do mesmo controller, podemos usar:

L10N com o bundle do Localization

Para forçar a tradução dos parâmetros do método i18n(), basta injetar o Localization e passar o seu bundle para o contrutor do Validations():

private final Validator validator;
private final Localization localization;

public UsuarioController(Validator validator, Localization localization) {
    this.validator = validator;
    this.localization = localization;
}
validator.checking(new Validations(localization.getBundle()) { {
    that(usuario.getIdade() >= 18, "usuario.idade", "maior.que", i18n("usuario.idade"), 18);
} });

Repare que aqui está sendo passado manualmente o ResourceBundle, dessa forma a key "usuario.idade" será traduzida corretamente na troca do Locale.

Mostrando os erros de validação no JSP

Quando existem erros de validação, o VRaptor coloca um atributo na requisição chamado errors contendo a lista de erros, então você pode mostrá-los na sua JSP de um jeito parecido com:

<c:forEach var="error" items="${errors}">
    ${error.category} - ${error.message}<br />
</c:forEach>