Validate complex object on GET request - spring

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
)

Related

#NotNull not working on nested object properties in a Kotlin class even though #Valid is added

I have a spring-boot app in Kotlin. I have a model called MyModel with just two field : a, b
NotNull validation works fine on a but it doesn't work on b although I've added #valid on top of b.
So when I don't pass b in my api request, it doesn't invalidate the model, although it's invalid (since b is missing).
I really don't know why it doesn't work. if I remove ? to enforce not nullness, then Jackson would throw exception when trying to deserializing the model. So I cannot make it nun-nullable (by removing ? from b). Here is my model.
data class MyModel(
#NotNull(message = "a is mandatory")
lateinit var a: String
#Valid
#NotNull(message = "b is mandatory")
var b: MyNestedModel?
)
data class MyNestedModel(
#NotNull(message = "MyNestedModel.a is mandatory")
var a: Date,
#NotNull(message = "MyNestedModel.b is mandatory")
var b: String,
#NotNull(message = "MyNestedModel.c is mandatory")
var c: String
)
And here is the my controller code :
#RestController
class MyController {
#Autowired
lateinit var validator: Validator
#PostMapping("/aa")
fun sortingRequest(#RequestBody req: MyModel): ResponseEntity<Any> {
val validationResult = validator.validate(req)
return if (validationResult.isEmpty()) {
ResponseEntity<Any>(null, HttpStatus.CREATED)
} else {
ResponseEntity<Any>(HttpErrorResponse("Bad input parameter", validationResult.map {
it.messageTemplate
}), HttpStatus.CREATED)
}
}
}
Any help would be greatly appreciated.
I've found the answer myself. Posting it in case might be useful for others.
from here I learned that I should prefix my annotation with get so that they can work :
To make the Spring validation work, you should explicitly mention the location to apply the annotation like #field:annotation or #get:annotation in the constructor. There’re many locations where the annotation could be placed in this context: Kotlin property, backing field, getter method, constructor parameter, setter method, or setter parameter.
The field: makes it explicit the annotation is to be applied to the backing field of the property.
So I changed #NotNull(message = "b is mandatory") to #get:NotNull(message = "b is mandatory") and it worked.
data class MyModel(
#NotNull(message = "a is mandatory")
lateinit var a: String
#Valid
#get:NotNull(message = "b is mandatory")
var b: MyNestedModel?
)
data class MyNestedModel(
#NotNull(message = "MyNestedModel.a is mandatory")
var a: Date,
#NotNull(message = "MyNestedModel.b is mandatory")
var b: String,
#NotNull(message = "MyNestedModel.c is mandatory")
var c: String
)
If you want to have it nullable on the class level and just not allow the nullability from an annotation perspective, I would recommend using #RequestEntity instead of #RequestBody and parse the request body that way. This way you can leave you class non-nullable and handle the parsing error that Jackson throws gracefully if no b is supplied.
I had something similar recently, which was caused by forgetting to include the validator library in my dependencies. The annotations were there, but they were just completely ignored.
This may not be applicable in your situation, but I fixed it by including this into my Gradle file:
implementation("org.springframework.boot:spring-boot-starter-validation")

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

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

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

spring validation in composition

I have the following data classes:
data class User (
#field:NotEmpty
val firstName: String?
#field:NotEmpty
val lastName: String?
)
data class Expert (
#field:NotEmpty
val name: String?
#field:NotNull
val contact: User?
)
And I would like to use my rest API endpoint to create an expert with spring validation:
#RestController
#RequestMapping("/api/experts")
class ExpertController(private val expertService: ExpertService) {
#PostMapping
#ResponseStatus(HttpStatus.CREATED)
fun create(#Valid #RequestBody dto: Expert) = expertService.create(dto)
}
Validation on name and contact fields works fine. But validation on firstName and lastName fields (User class) doesn't work. Is it a normal behaviour? I can't use validation on composition? Why? Or am I missing something?
In order for the User to be validated if it is contained within an Expert, you will need to add the #Valid annotation to it, so Spring's Validator knows to keep checking, otherwise it will stop.
Try this (untested):
data class Expert (
#field:NotEmpty
val name: String?
#field:NotNull
#field:Valid
val contact: User?
)

Resources