#RequestBody and #Valid not validating empty/blank string field in kotlin object - spring

My request is parsed into the Kotlin object that is the request body in the controller. Currently, if the fields are null - a validation exception is thrown which is exactly what i want.
However, I am using #NotBlank and this doesn't seem to validate the way it's meant to against "" or " ". What am I missing?
#NotEmpty also doesn't check against "". They both seem to just check against null, which is only part of what I'm looking for.
Essentially I am looking for the request not to be null, the list field not to be null, the inner BumblebeeEvent in the list not to be null, and each Event to have it's fields validated (e.g. not null, "" or " ").
Here is my controller:
#Controller
class BumblebeeEventController {
#PostMapping("/publish")
fun eventIntake(#RequestBody #Valid payload: BumblebeeEventList) : ResponseEntity<String>{
return ResponseEntity("Published ${eventList.size} event(s) successfully",HttpStatus.OK)
}
}
class Event(#NotBlank val localTime: String){}
class BumblebeeEvent(#NotNull val event: Event)
class BumblebeeEventList(#NotNull val events: List<BumblebeeEvent>){}
Thanks in advance

try with
class Event(#NotBlank val localTime: String){}
class BumblebeeEvent(#NotNull #Valid val event: Event)
class BumblebeeEventList(#NotNull #Valid val events: List<BumblebeeEvent>){}
You must indicate that the validation must go also in the nested object

Related

Validate complex object on GET request

I'm trying to apply input validation on a complex parameter of a get request.
What I have:
data class FilterDTO (
#Valid
#NotNull
val id: Long? = null,
#NotNull
val code: String? = null
)
#Validated
#RestController
#RequestMapping("/foo")
class FooController {
#GetMapping
fun get(
#Valid filter: FilterDTO,
#Valid #NotNull #RequestParam("bar") bar: String?
) {
// ...
}
}
The above endpoint validates correctly the #NotNull on the bar parameter but it seems to ignore the validation on the complex FilterDTO object.
I have tried:
Adding #Valid on FilterDTO's properties (even with Kotlin's #field: and #get:)
Adding #Validated (??) on the FilterDTO whole class
Couldn't make it work.
Is it possible to have a complex get parameter validated?
Thanks
Just found the problem. For anyone getting here, I should be annotating with #field: the validations I want. So simply changing my DTO to this worked:
data class FilterDTO (
#field:NotNull
val id: Long? = null,
#field:NotNull
val code: String? = null
)

Spring boot rest requestbody and #valid not working when object is null/empty

I am trying to apply not null validation on an attribute of my request which is instructedAmount but it is not working. I have a Spring Boot (V2.3.0.RELEASE) application with the following endpoints:
#Validated
public class TestController {
#PostMapping(value = "/test/pay")
public ResponseEntity<IPSPaymentResponse> validatePayt(#Valid #RequestBody InstantPaymentRequest instantPaymentRequest) {
log.debug("start validatePayment method {}", instantPaymentRequest);
....
The InstantPaymentRequest is as follows:
#Data
#Validated
public class InstantPaymentRequest {
#Valid
private PaymentIdentificationRequest paymentIdentification;
#NotBlank(message = "transactionTypeCode.required")
private String transactionTypeCode;
#Valid
private InstructedAmount instructedAmount;
#Valid
private CustomerRequest debtor;
The instructed amount is as follows:
#Data
public class InstructedAmount {
#NotBlank(message = "currency.required")
private String currency;
#NotBlank(message = "value.required")
private String value;
}
Basically when the instructedAmount is provided in the payload but for example I miss currency attribute in payload, the validation is working fine, the non null error message is displayed correctly and my rest controller endpoint is not called.
However when instructedAmount is not provided in the payload, no mandatory error message is displayed and my rest endpoint is called, it this the correct way or I am missing something?
I thought since attribute of InstructedAmount cannot be null then InstructedAmount also cannot be null/empty.
How to add InstructedAmount not null validation in the above scenario with annotation?
Use #NotNull together with #Valid:
#NotNull
#Valid
private InstructedAmount instructedAmount;
From https://docs.jboss.org/hibernate/stable/validator/reference/en-US/html_single/#section-object-graph-validation:
Note that null values are getting ignored during cascaded validation.

Spring Validation: ConstraintViolationException for #Pattern due to password encoding

I'm just implementing a basic CRUD service where a user can be created in the database with their password matching a certain regex and being encoded using BCryptPasswordEncoder.
My tests are failing due to a ConstraintViolationException on the password saying that it does not satisfy the regex requirement:
javax.validation.ConstraintViolationException: Validation failed for classes [com.hoaxify.hoaxify.user.User] during persist time for groups [javax.validation.groups.Default, ]
List of constraint violations:[
ConstraintViolationImpl{interpolatedMessage='must match "^(?=.*[a-z])(?=.*\d)(?=.*[A-Z]).{8,50}$"', propertyPath=password, rootBeanClass=class com.hoaxify.hoaxify.user.User, messageTemplate='{javax.validation.constraints.Pattern.message}'}
It wasn't getting caught in my #ExceptionHandler since it was throwing a ConstraintViolationException and not a MethodArgumentNotValidException. I debugged and found that, while it was trying to match to the given regex, the value for the password itself was showing as:
$2a$10$pmRUViwj3Ey4alK0eqT1Dulz4BpGSlSReHyBR28K6bIE4.LZ7nYWG
while the password being passed in was:
P4ssword
So it appears the validation is being run on the encrypted password and not the raw password. I thought the validation should occur on the object received in the createUser method - before any other manipulation occurred.
Any help on why this is happening and how to fix would be greatly appreciated.
Note:
Validation works for all other fields
UserController
#RestController
#RequestMapping("{my/path}")
class UserController {
#Autowired
lateinit var userService: UserService
#PostMapping
fun createUser(#Valid #RequestBody user: User): GenericResponse {
userService.save(user)
return GenericResponse("Saved user")
}
#ExceptionHandler(MethodArgumentNotValidException::class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
fun handleValidationException(exception: MethodArgumentNotValidException, request: HttpServletRequest): ApiError {
val error = ApiError(400, "Validation error", request.servletPath)
val bindingResult = exception.bindingResult
bindingResult.fieldErrors.forEach {
error.validationErrors[it.field] = it.defaultMessage ?: "invalid"
}
return error
}
}
User
#Entity
class User(
#Id
#GeneratedValue
val id: Long,
#field:NotBlank
#field:Size(min = 4, max = 50)
var username: String,
#field:NotBlank
#field:Size(min = 4, max = 50)
var displayName: String,
#field:NotBlank
#field:Pattern(regexp = """^(?=.*[a-z])(?=.*\d)(?=.*[A-Z]).{8,50}$""")
var password: String
)
UserService
#Service
class UserService(
private val userRepository: UserRepository,
private val passwordEncoder: BCryptPasswordEncoder = BCryptPasswordEncoder()
) {
fun save(user: User): User {
user.password = passwordEncoder.encode(user.password)
return userRepository.save(user)
}
}
UserControllerTest
(relevant test)
#Test
fun postUser_whenUserIsValid_receiveOk() {
val user = User(
0,
"test-user",
"test-display",
"P4ssword"
)
val response: ResponseEntity<Any> = testRestTemplate.postForEntity(API_USERS_BASE, user, Any::class.java)
assertThat(response.statusCode).isEqualTo(HttpStatus.OK)
}
The problem is that you use the same entity in the controller as in the service. So in the controller, it works as you expect. But in the service, you update the unencrypted password with the encrypted one and save that to the database. When you save to the database, the validation annotations are also checked, triggering the ConstraintViolationException.
The best option is to create a separate object for the controller. For example, create a CreateUserRequest class which is similar to the User entity, but only contains the fields that the controller needs. You can add your validation annotations there. Then in the service, convert the CreateUserRequest instance to a User entity. On the user class, remove the #Pattern validation since you don't want to validate the encrypted password.

What does #get:NotNull mean in Kotlin?

I read a code generated by khipster and in one dataclass I found such fragment:
import javax.validation.constraints.NotNull
data class MyDTO(
var id: Long? = null,
#get: NotNull
var name: String? = null,
What does #get:NotNull annotation mean? As far as I understand #get means that I want to annotate the getter of name property and NotNull is a validation annotation which mean that the thing can't be set to null. But how the two work together? It doesn't make any sense to annotate getter with annotation which means this can't be set to null, because getter can't be set. It would make more sens to use NotNull annotation on setter.
#NotNull on a method means it can't return null. So in particular annotating a setter with it makes no sense; annotating the setter's parameter does.
If you use the decompile feature of IntelliJ ( please check this answer )
Kotlin Code:
#get: NotNull
var uid: String? = null
Decompiled Code:
import javax.validation.constraints.NotNull;
import org.jetbrains.annotations.Nullable;
#Nullable
private String uid;
#NotNull
#Nullable
public final String getUid() {
return this.uid;
}
public final void setUid(#Nullable String var1) {
this.uid = var1;
}
What does #get:NotNull annotation mean?
A quick answer: Please put #NotNull annotation on the top of the getter function
!! Please be aware that there is also #Nullable annotation added to the getter function because of the "?" at the end of the variable definition
As you notice from import it is added by IntelliJ
As detailed answer: I could redirect you to "Use-Site Target Declarations"
Bealdung
Blog post
Finally, I would like to express my experience on that, I had both #Nullable and #NotNull annotation on uid field (you could see on decompiled code), I could set that field to null

Spring not null validation throwing HttpMessageNotReadableException instead of MethodArgumentNotValidException in kotlin

I'm making and simple application in Kotlin using Spring but I'm having a problem with the validation.
I have this entity class:
#Entity
#Table(name = "category")
data class Category(
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long?,
#field:NotNull #field:NotEmpty val name: String)
And my controller function like this:
#PostMapping
#ResponseStatus(HttpStatus.CREATED)
fun create(#Valid #RequestBody category: Category): ResponseEntity<Category>
create have some code, but it is irrelevant for the question, my problem is with the request body validation. If I send a category with an empty name field, it is thrown a MethodArgumentNotValidException exception, but if I send null to the field name, the exception thrown HttpMessageNotReadableException instead. Does anyone knows if it is possible to make passing null to a field marked with #NotNull to also throw MethodArgumentNotValidException in Kotlin.
So your problem is you specify the name field as not nullable, by default jackson module for kotlin will check it and throw HttpMessageNotReadableException which cause by MissingKotlinParameterException during json mapping process. If you mark name filed as nullable json mapping will passed and get to the spring validation phase with #Valid then we will get MethodArgumentNotValidException
#Entity
#Table(name = "category")
data class Category(
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long?,
#field:NotNull #field:NotEmpty val name: String?)
You can handle this issue by providing HttpMessageNotReadableException handler
and then checking if the underlying cause is MissingKotlinParameterException.
After that, you can provide custom validation error. I'm using zalando-problem, so syntax is a bit different from vanilla spring, but you get the idea:
#ExceptionHandler
override fun handleMessageNotReadableException(
exception: HttpMessageNotReadableException,
request: NativeWebRequest
): ResponseEntity<Problem> {
// workaround
val cause = exception.cause
if (cause is MissingKotlinParameterException) {
val violations = setOf(createMissingKotlinParameterViolation(cause))
return newConstraintViolationProblem(exception, violations, request)
}
return create(Status.BAD_REQUEST, UnableToReadInputMessageProblem(), request)
}
private fun createMissingKotlinParameterViolation(cause: MissingKotlinParameterException): Violation {
val name = cause.path.fold("") { jsonPath, ref ->
val suffix = when {
ref.index > -1 -> "[${ref.index}]"
else -> ".${ref.fieldName}"
}
(jsonPath + suffix).removePrefix(".")
}
return Violation(name, "must not be null")
}
This way you get get nice output with proper constraint error.
You may try to declare #ExceptionHandler for MissingKotlinParameterException directly (though I've tried, but it didn't some reason), but I can't guarantee it'll work.
Code samples for path parsing are taken from here

Resources