When calling /orders/{order_id}/discounts/{discount_id} :
The thing is I want Jakarta annotations to validate my fields first, and then to run custom validator (DiscountValidator) assuming discountValue for example, is not null.
@PreAuthorize("hasRealmRoles('INTERNAL_USER','ROBOTIC_USER') or orderBelongsToAccount(#orderIdAsString)")
@PutMapping(path = "orders/{order_id}/discounts/{discount_id}", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<OrderDiscountDto> updateOrderDiscount(@Valid @RequestBody OrderDiscountRequest orderDiscountRequest,
@UUIDConstraint @PathVariable("order_id") String orderIdAsString,
@UUIDConstraint @PathVariable("discount_id") String discountIdAsString);
@AllArgsConstructor
@NoArgsConstructor
@Data
@Builder
@ValidDiscount
public class OrderDiscountRequest {
@Schema(description = "The decimal amount, deducted from order's total value", name = "discount_value",
example = "31.00", type = "string", format = "string")
@JsonProperty("discount_value")
@DecimalMin("0.0")
@Digits(integer = 10, fraction = 2)
@NotNull(message = "Discount value cannot be null. Provide field value.")
private BigDecimal discountValue;
@Schema(description = "Object containing info regarding discount policy", name = "policy",
implementation = DiscountPolicy.class, enumAsRef = true)
@JsonProperty("policy")
@NotNull(message = "Discount Policy cannot be null. Provide field value.")
private DiscountPolicy policy;
}
@Constraint(validatedBy = DiscountValidator.class)
@Target({ElementType.PARAMETER, ElementType.TYPE, ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidDiscount {
String message() default "Invalid discount request information";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
@Slf4j
public class DiscountValidator implements ConstraintValidator<ValidDiscount, OrderDiscountRequest> {
@Override
public boolean isValid(OrderDiscountRequest discountRequest, ConstraintValidatorContext context) {
validatePercentagePolicy(discountRequest);
return true;
}
private void validatePercentagePolicy(OrderDiscountRequest discountRequest) {
if (DiscountPolicy.PERCENTAGE.equals(discountRequest.getPolicy()) &&
discountRequest.getDiscountValue().compareTo(MAX_PERCENTAGE) == 1) {
log.warn("Discount percentage value greater than 100.00");
throw new ConstraintViolationException(ValidationMessages.ORDER_DISCOUNT_PERCENTAGE_VALUE_EXCEEDED, new HashSet<>());
}
}
}
@Configuration
public class ValidationConfiguration {
@Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
MethodValidationPostProcessor processor = new MethodValidationPostProcessor();
processor.setBeforeExistingAdvisors(true);
return processor;
}
}
Add annotation @GroupSequence to validate using Jakarta annotations first, before custom validator.
@GroupSequence({OrderDiscountRequest.class, DiscountValidator.class})
public class OrderDiscountRequest {