CategoryCreateValidator.java
package com.albertsilva.dev.asjcatalog.validation.category.validator;
import java.util.ArrayList;
import java.util.List;
import com.albertsilva.dev.asjcatalog.dto.category.request.CategoryCreateRequest;
import com.albertsilva.dev.asjcatalog.repository.CategoryRepository;
import com.albertsilva.dev.asjcatalog.validation.category.annotation.CategoryCreateValid;
import com.albertsilva.dev.asjcatalog.web.exception.response.FieldMessage;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
/**
* Validator de {@link CategoryCreateValid}: uma única verificação sobre
* {@code CategoryCreateRequest}.
*
* <p>
* Se o nome for {@code null} ou em branco, nada é verificado. Caso contrário,
* normaliza o nome ({@code trim()} + {@code toLowerCase()}) e consulta
* {@code CategoryRepository.existsByNameIgnoreCase}; se existir, registra
* {@code {category.name.unique}} no campo {@code name}. O nome do DTO não é
* modificado (o mapper o grava como recebido). Consulta o banco; não usa HTTP
* nem autenticação. Usa {@code FieldMessage} (pacote de exceções da camada web)
* como estrutura de acúmulo.
* </p>
*/
public class CategoryCreateValidator implements ConstraintValidator<CategoryCreateValid, CategoryCreateRequest> {
/** Repositório usado para verificar a existência de categoria com o mesmo nome. */
private final CategoryRepository repository;
/**
* Construtor que recebe o repositório de categorias
* para realizar as validações necessárias.
*
* @param repository repositório utilizado para verificar
* a existência de categorias com o mesmo nome
*/
public CategoryCreateValidator(CategoryRepository repository) {
this.repository = repository;
}
/**
* Verifica se já existe categoria com o nome informado.
*
* @param dto dados de criação
* @param context contexto usado para registrar a violação no campo
* {@code name}
* @return {@code true} se o nome estiver livre (ou em branco)
*/
@Override
public boolean isValid(CategoryCreateRequest dto, ConstraintValidatorContext context) {
List<FieldMessage> errors = new ArrayList<>();
validateUniqueName(dto, errors);
addErrors(errors, context);
return errors.isEmpty();
}
/**
* Valida se já existe uma categoria cadastrada
* com o mesmo nome informado.
*
* <p>
* Antes da verificação, o nome é normalizado:
* <ul>
* <li>{@code trim()} (remove espaços apenas nas extremidades)</li>
* <li>Convertendo para letras minúsculas</li>
* </ul>
*
* <p>
* Caso o nome já exista, uma mensagem de erro
* é adicionada à lista de erros.
*
* @param dto objeto contendo os dados da categoria
* @param errors lista responsável por armazenar
* os erros encontrados durante a validação
*/
private void validateUniqueName(CategoryCreateRequest dto, List<FieldMessage> errors) {
if (dto.name() == null || dto.name().isBlank()) {
return;
}
String normalizedName = dto.name().trim().toLowerCase();
boolean categoryAlreadyExists = repository.existsByNameIgnoreCase(normalizedName);
if (categoryAlreadyExists) {
errors.add(new FieldMessage("name", "{category.name.unique}"));
}
}
/**
* Adiciona ao contexto de validação todos os erros
* encontrados durante o processo de validação.
*
* <p>
* O método desabilita a mensagem padrão do Bean Validation
* para permitir o registro de mensagens customizadas
* associadas a campos específicos.
*
* @param errors lista contendo os erros encontrados
* @param context contexto utilizado para registrar
* as violações de validação
*/
private void addErrors(List<FieldMessage> errors, ConstraintValidatorContext context) {
if (errors.isEmpty()) {
return;
}
context.disableDefaultConstraintViolation();
for (FieldMessage error : errors) {
context.buildConstraintViolationWithTemplate(error.message()).addPropertyNode(error.fieldName())
.addConstraintViolation();
}
}
}