Bean Validation on complex objects - Kotlin Spring Boot - spring-boot

I have a data class that is being validated except for the contents of the lists
data class EmailDto(
#get:Valid
#get:NotEmpty
val recipients: List<#Email String>,
#get:NotBlank
val subject: String,
#get:Valid
val cc: List<#Email String> = listOf(),
#get:Valid
val bcc: List<#Email String> = listOf(),
)
So my problem is if I use a value of listOf() for recipients the validation fires and gives me an exception, whereas listOf("test.com") does not cause an exception when it should since the string value isn't a valid email address.
I've tried all different kinds of targets on the annotations and omitted them but I can't seem to get any validation to work on complex fields. Does anyone have any insight on this? I haven't been able to really track anything down searching online.

Related

How to POST entity with relationships in REST endpoint - Kotlin Spring Data

I followed this tutorial to create a basic web app in Kotlin using Spring Boot. However, I fail to POST new entities with a many-to-one relationship to an existing resource.
My code:
#Entity
class Song(
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long? = null,
var title: String,
#ManyToOne(fetch = FetchType.EAGER)
var addedBy: User)
#Entity
class User(
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long? = null,
var email: String,
var displayName: String)
#RestController
#RequestMapping("/api/songs")
class SongController(private val repository: SongRepository) {
#PostMapping("/")
fun add(#RequestBody song: Song) =
repository.save(song)
This answer and others point out that you can reference another resource using its URI, but sending the following request:
{
"title": "Some title",
"addedBy": "http://localhost:8080/api/users/1"
}
gets me an errors with stack trace org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot construct instance of 'com.example.springboot.User' (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('http://localhost:8080/api/users/1'); nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of 'com.example.springboot.User' (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('http://localhost:8080/api/users/1')\n at [Source: (PushbackInputStream); line: 6, column: 13] (through reference chain: com.example.springboot.Song[\"addedBy\"])
I got out of this that somewhere between Jackson/Hibernate/Spring Data it fails to convert the User resource URI into a User entity, but I'm in the dark where this magic should happen.
It seems to be an issue that occurs with Kotlin specifically. All the suggestions here on SO do not solve this specific error and the tutorial itself stops just short of dealing with relationships. If it's not the right approach at all to handle relationships this way I'd be eager to know what the preferred practice would be.
The tutorial is using HATEOAS. See the request body where they are referencing the corresponding child entity by using
"books" : { "href" : "http://localhost:8080/authors/1/books" }
Meaning you should also apply this pattern to your request. Otherwise this will not work. HATEOAS allows you to directly reference the related child entities by their corresponding resource path but you need to keep the necessary structure which your posted request body is missing. Further you must support HATEOAS in your WebService / WebApi / Spring Boot Project.
What you could do:
{
"title": "Some title",
"addedByUserId": "1"
}
Then
#PostMapping("/")
fun add(#RequestBody song: Song) =
val userEntity = userRepository.findById(song.getAddedByUserId())
Song newSong = new SongEntity();
// map props
newSong.setUser(userEntity)
repository.save(song)
That code does not work but I hope you get the idea.
Further
In your code you are treating the Request Body as an Entity. Please consider to separate your incoming Class and your Entity class. This would make several things easier.
I think you're missing jackson's kotlin module, it's exactly what it was created for.
https://github.com/FasterXML/jackson-module-kotlin
Just adding this dependency in your project will cause spring to autoconfigure your object mapper with this new module. If you have a Bean with your own created objectMapper then you need to configure it manually, there's a section about this in module's README.md

Spring validation for Kotlin primitives

I have created simple Spring Boot project with using Kotlin 1.4.10.
I have simple DTO in the project:
data class TestRequest(
#field:NotNull val id: Int,
val optionalId: Int?,
val name: String,
val optionalName: String?,
#field:NotNull val value: Double,
val optionalValue: Double?,
val nested: NestedRequest,
val optionalNested: NestedRequest?
)
data class NestedRequest(
#field:NotNull val nestedId: Long,
val nestedOptionalId: Long?,
val nestedName: String,
val optionalNestedName: String?
)
I am wondering, what is best practice to write Kotlin DTO's and validate them?
From one side, Kotlin allows to mark fields as not-null, which seems to be convenient for validation.
From another, in case of Kotlin numeric types (Int, Long, Double etc), which seems to have default value, as Java primitives do, so checking of nullability does not work for such fields unlike string ones.
If I use #JsonProperty(required = true), nullability will be checked by Jackson and not by validator, so this approach is also incorrect.
As a result I've got a question - is there a proper way of validating Kotlin DTO's at all?
As you have noticed, it is hard to validate kotlin primitive types for nulability, because they have default values.
I would say that using a combination of Jackson (for nullability of primitive types) and Javax validation (stuff like min/max value) is fine.
However, if you don't want to use Jackson validation, you can validate primtive types by setting the type of the variable as nullable but annotating it as #NotNull.
For example:
import javax.validation.Valid
import javax.validation.constraints.NotNull
data class MyClass(
#get:Valid
#get:NotNull
val someInt: Int?,
val someText: String
)
Now, because the type is nullable (in this example Int?) Jackson won't insert a default value for someInt, therefore someInt is going to have a value of null. After that, when the object gets validated, an error will be thrown because the value of someInt is null.
For example, if we have the following #PostMapping:
#PostMapping("/test")
fun testFunction(#RequestBody #Valid data: MyClass) {
print(data)
}
Sending a POST request with body:
{
"someText": "wow"
}
Will return an error like this one:
"timestamp": "2020-10-02T15:22:53.361+00:00",
"status": 400,
"error": "Bad Request",
"trace": "org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public void main.api.TestPublicController.myObject(main.api.MyClass): [Field error in object 'myClass' on field 'someInt': rejected value [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

Kotlin data class No String-argument constructor with spring data rest

I'm using Spring data rest with Kotlin and if I use data classes the associations via uri stops working with the error no String-argument constructor/factory method to deserialize from String value
Data class
#Entity
data class Comment(
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long = 0,
var author: String? = null,
var content: String? = null,
#ManyToOne
var post: Post? = null) {
}
If I use a simple class instead the association works fine.
#Entity
class Comment {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY) var id: Long = 0
var author: String? = null
var content: String? = null
#ManyToOne
var post: Post? = null
}
The association is done via a POST request {"author":"John Doe","content":"Dummy Content", "post":"http://localhost:8080/post/33"}
Any ideia why I have this error when I use a data class and what can I do to use the association creation via uri and keep using data classes?
I did some investigation, and turns out Spring Data Rest uses a custom Jackson module to deserialize JSON into JPA entities: it uses PersistentEntityJackson2Module class and using the inner class UriStringDeserializer to resolve the concrete entities from entity URI references, http://localhost:8080/post/33 in your example.
Problem is, this custom deserialization only kicks in when the "standard deserialization" of Jackson is triggered: The one that uses empty constructor, then using setters to resolve & set the fields. At that moment, UriStringDeserializer converts the string into the concrete entity - Post instance of your example.
When you use a data class, the class neither has an empty constructor nor setters, therefore in BeanDeserializer#deserializeFromObject method of Jackson, it branches into if (_nonStandardCreation) being true, from there the call goes into BeanDeserializerBase#deserializeFromObjectUsingNonDefault , but not handed over to PersistentEntityJackson2Module anymore, and directly failing due to type mismatch between the constructor argument and the json value.
It seems you need to create a feature request for it to be implemented. If you decide to implement yourself, providing a _delegateDeserializer to the BeanDeserializer might be a start (not sure).
However, I don't know how JPA itself plays with data classes in the first place - after all it tracks the entity state changes, but a data class cannot have state changes. So, it might not be possible to use data classes after all - better to keep in mind.
Note: You probably cannot simply extend/override PersistentEntityJackson2Module because it is registered to multiple beans in RepositoryRestMvcConfiguration

How to configure Spring MVC to validate constructor parameters of controller method arguments

I'm writing a project in Kotlin and have this in a controller:
#PostMapping("/token")
fun generateToken(#RequestBody #Valid credentials: Credentials) { /* something */ }
data class Credentials(#Email #NotBlank val email: String,
#NotBlank val password: String)
By default #Valid annotation tells Spring to validate object fields. But Kotlin places constraint annotations on the constructor parameters, so validation doesn't work. To make it work I have to define use-site targets for annotations:
data class Credentials(#field:Email #field:NotBlank val email: String,
#field:NotBlank val password: String)
which is annoying and adds visual garbage. Is it possible to configure Spring to validate constructor parameters?
There isn't a whole lot you can do. You can make it look a little better by combining annotations for each field, e.g.:
data class Credentials(#field:[Email NotBlank] val email: String,
#field:NotBlank val password: String)
Other than that, your only other options are:
Manually configured Spring validation classes
Validating the data within your code body

Resources