-
Notifications
You must be signed in to change notification settings - Fork 41.7k
Closed as not planned
Labels
status: declinedA suggestion or change that we don't feel we should currently applyA suggestion or change that we don't feel we should currently apply
Description
🐞 Bug report (please don't include this emoji/text, just add your details)
Hello.
It seems like that custom constraints are simply ignored in version 3.5.8 when applied to parameters of interface default methods.
I've the following custom constraint:
@Documented
@Retention(RUNTIME)
@Target({TYPE, RECORD_COMPONENT, PARAMETER, METHOD})
@Constraint(validatedBy = ValidAdjustmentDateValidator.class)
public @interface ValidAdjustmentDate {
String message() default "Invalid date combination";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
import com.google.type.Date;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.time.DateTimeException;
import java.time.LocalDate;
import java.time.Year;
public class ValidAdjustmentDateValidator implements ConstraintValidator<ValidAdjustmentDate, Date> {
private static final Logger logger = LoggerFactory.getLogger(ValidAdjustmentDateValidator.class);
static final int MIN_YEAR = LocalDate.EPOCH.getYear();
@Override
public boolean isValid(Date adjustmentDate, ConstraintValidatorContext context) {
String validationErrorMessage = null;
int maxYear = getMaxYear();
if (adjustmentDate.getYear() < MIN_YEAR || adjustmentDate.getYear() > maxYear) {
validationErrorMessage = "Year must be between %d and %d: year=%d".formatted(MIN_YEAR, maxYear, adjustmentDate.getYear());
} else {
try {
//noinspection ResultOfMethodCallIgnored -> needed just to validate the date itself
LocalDate.of(adjustmentDate.getYear(), adjustmentDate.getMonth(), adjustmentDate.getDay());
} catch (DateTimeException e) {
logger.debug(e.toString(), e);
validationErrorMessage = "Invalid date combination: year=%d, month=%d, day=%d".formatted(adjustmentDate.getYear(), adjustmentDate.getMonth(), adjustmentDate.getDay());
}
}
if(validationErrorMessage != null) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(validationErrorMessage).addConstraintViolation();
}
return validationErrorMessage == null;
}
public static int getMaxYear() {
return Year.now().plusYears(1).getValue();
}
}which is applied on the following Converter interface:
@Validated
@Mapper(config = MappingConfiguration.class, uses = ListingIdentifierToListingConverter.class)
public interface GetAdjustmentsRequestToAdjustmentRequestConverter extends Converter<GetAdjustmentsRequest, AdjustmentRequest> {
@Valid
@Override
@Mapping(source = "listingsList", target = "listings")
@Mapping(expression = "java(toLocalDate(source.getToDate()))", target = "to")
@Mapping(expression = "java(toLocalDate(source.getFromDate()))", target = "from")
AdjustmentRequest convert(GetAdjustmentsRequest source);
default LocalDate toLocalDate(@ValidAdjustmentDate Date date) {
return date == null ? null : LocalDate.of(date.getYear(), date.getMonth(), date.getDay());
}
}The implementation that gets generated looks, to me, correct:
@Generated(
value = "org.mapstruct.ap.MappingProcessor",
comments = "version: 1.6.3, compiler: javac, environment: Java 25 (Azul Systems, Inc.)"
)
@Component
public class GetAdjustmentsRequestToAdjustmentRequestConverterImpl implements GetAdjustmentsRequestToAdjustmentRequestConverter {
private final ListingIdentifierToListingConverter listingIdentifierToListingConverter;
@Autowired
public GetAdjustmentsRequestToAdjustmentRequestConverterImpl(ListingIdentifierToListingConverter listingIdentifierToListingConverter) {
this.listingIdentifierToListingConverter = listingIdentifierToListingConverter;
}
@Override
public AdjustmentRequest convert(GetAdjustmentsRequest source) {
if ( source == null ) {
return null;
}
Set<Listing> listings = null;
boolean cumulateDailyAdjustments = false;
listings = listingIdentifierListToListingSet( source.getListingsList() );
cumulateDailyAdjustments = source.getCumulateDailyAdjustments();
LocalDate to = toLocalDate(source.getToDate());
LocalDate from = toLocalDate(source.getFromDate());
AdjustmentRequest adjustmentRequest = new AdjustmentRequest( listings, from, to, cumulateDailyAdjustments );
return adjustmentRequest;
}
protected Set<Listing> listingIdentifierListToListingSet(List<ListingIdentifier> list) {
if ( list == null ) {
return new LinkedHashSet<Listing>();
}
Set<Listing> set = LinkedHashSet.newLinkedHashSet( list.size() );
for ( ListingIdentifier listingIdentifier : list ) {
set.add( listingIdentifierToListingConverter.convert( listingIdentifier ) );
}
return set;
}
}For constraints defined on objects themselves like, for example:
import org.hibernate.validator.constraints.Range;
public record Listing(@Range(min = 1, max = MAX_UINT) long valor, @Range(min = 1, max = MAX_USHORT) int marketCode, @Range(min = 1, max = MAX_USHORT) int currencyCode) {
static final long MAX_UINT = (1L << Integer.SIZE) - 1;
static final int MAX_USHORT = (1 << Short.SIZE) - 1;
}the validation is working as expected.
I would be expecting to not be working given that the method is within the same class being proxied, but MethodValidationPostProcessor is there and it should therefore work to my knowledge.
Metadata
Metadata
Assignees
Labels
status: declinedA suggestion or change that we don't feel we should currently applyA suggestion or change that we don't feel we should currently apply