Reputation: 153
I have a simple Rest Controller in which I accept a validated object:
@RestController
@RequestMapping("v1/games")
public class GameController {
@PostMapping
public GameCreateOutWeb create(@RequestBody @Valid GameWeb dto) {
//some request handling
}
}
This is what the GameWeb
DTO looks like:
public record GameWeb(@NotBlank String localId, @NotBlank String name,
List<@Valid @Slot SlotWeb> slots) {
}
This is what the SlotWeb
DTO looks like:
public record SlotWeb(@NotNull SlotColor color, @NotNull SlotStatus status, PlayerWeb player) {
public record PlayerWeb(Long id, String name) {
}
}
public enum SlotStatus {
OPEN((short) 0), CLOSED((short) 1), OCCUPIED((short) 2);
private final short statusCode;
//constructor and getter
}
public enum SlotColor {
RED((short) 0), BLUE((short) 1), TEAL((short) 2), PURPLE((short) 3), YELLOW((short) 4), ORANGE((short) 5);
private final short colorCode;
//constructor and getter
}
@Slot
annotation uses a custom validator:
@Target(TYPE_USE)
@Retention(RUNTIME)
@Constraint(validatedBy = {SlotValidator.class})
public @interface Slot {
String message() default "If slotStatus == OCCUPIED(2) then player field must not be null.";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class SlotValidator implements ConstraintValidator<Slot, SlotWeb> {
@Override
public boolean isValid(SlotWeb slotWeb, ConstraintValidatorContext constraintValidatorContext) {
if (slotWeb == null) {
return false;
}
if (slotWeb.status().equals(SlotStatus.OCCUPIED)) {
return slotWeb.player() != null;
}
return slotWeb.player() == null;
}
}
Until a certain time I was satisfied that after an unsuccessful validation of the list object an exception was thrown, which I could then handle. But now the requirements have changed and instead of handling an exception somewhere in the ControllerAdvice
I would like to just skip invalid entries in the list.
Here is a JSON example:
{
"localId": "id",
"name": "name",
"slots": [
{
"color": 0,
"status": 2,
"player": {
"id": 1,
"name": "1"
}
}, // perfectly valid
{
"color": 0,
"status": 0,
"player": {
"id": 1,
"name": "1"
}
}, // not valid. Skip
null // not valid. Skip
]
}
So the expected result should be (pseudocoded):
GameWeb("id", "name", ArrayList<>(SlotWeb(SlotColor.RED, SlotStatus.OCCUPIED, PlayerWeb(1, "1"))))
I wonder if there is a more convenient way to handle this than implementing my own validation at the service layer. Or maybe the most straightforward and most correct way would be to delete invalid list items during deserialization?
Upvotes: 0
Views: 61
Reputation: 24
I don't think that there is something like that available during validation and I also don't think there should. This would be a side-effect, that others will most likely not expect, as the validation framework should really only do validation.
I don't know the bigger picture of your project, but to me that sounds like something that should be fixed on the client side. If your domain can only handle a certain format, clients should be rejected when trying to pass anything invalid. For me it just feels a bit wrong to simply accept everything and then silently filter out whatever is invalid, without notifying the client about it.
If you think that is not an issue, you could still do the filtering at the controller or domain layer, but it should be something very explicit and not hidden within the validation logic.
Upvotes: 1