#Valid annotation is not validating the list of child objects - Spring Boot + Kotlin - spring-boot

I have a very similar problem to this topic #Valid annotation is not validating the list of child objects but trying to implement using Kotlin.
The #Valid annotation is not working for WordDto. Only the class MessageDto is evaluated.
#SpringBootApplication
class ValidationsApplication
fun main(args: Array<String>) {
runApplication<ValidationsApplication>(*args)
}
data class WordDto(
#field:Max(5)
val word: String
)
data class MessageDto(
#Valid
#field:NotEmpty
val words: List<WordDto>
)
#RestController
class Controller {
#PostMapping("/hello")
fun hello(#Valid #RequestBody messageDto: MessageDto) {
messageDto.words.map(System.out::println)
}
}
I've tried this approach too:
val words: List<#Valid WordDto>
Also wrote this test that should be passing:
#Test
fun `should validate child objects`() {
val dto = MessageDto(
words = listOf(
WordDto("Long Word that should fail")
)
)
val violations = validator.validate(dto)
assertThat(violations).isNotEmpty()
}
Dependencies:
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-validation")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
Any ideas of what could be missing?
The project can be found in Github

Following the #NikolaiShevchenko's answer, this is the solution.
data class MessageDto(
#field:Valid
#field:NotEmpty
val words: List<WordDto>
)

Related

Can't Autowire #Repository interface in Spring Boot

The problem appeared when I tried to migrate on WebFlux.
I have one package university. It contains 4 files: Document, Controller, Service and Repository.
#Document
data class University(
#Id
#JsonSerialize(using = ToStringSerializer::class)
var id: ObjectId?,
var name: String,
var city: String,
var yearOfFoundation: Int,
#JsonSerialize(using = ToStringSerializer::class)
var students: MutableList<ObjectId> = mutableListOf(),
#Version
var version: Int?
)
#Service
class UniversityService(#Autowired private var universityRepository: UniversityRepository) {
fun getAllUniversities(): Flux<University> =
universityRepository.findAll()
fun getUniversityById(id: ObjectId): Mono<University> =
universityRepository.findById(id)
}
#RestController
#RequestMapping("api/universities", consumes = [MediaType.APPLICATION_NDJSON_VALUE])
class UniversityController(#Autowired val universityService: UniversityService) {
#GetMapping("/all")
fun getAll(): Flux<University> =
universityService.getAllUniversities().log()
#GetMapping("/getById")
fun getUniversityById(#RequestParam("id") id: ObjectId): Mono<University> =
universityService.getUniversityById(id)
}
#Repository
interface UniversityRepository: ReactiveMongoRepository<University, ObjectId>, CustomUniversityRepository {
fun existsByNameIgnoreCase(name: String): Mono<Boolean>
fun removeUniversityById(id: ObjectId): Mono<University?>
fun findUniversitiesByNameIgnoreCase(name: String): Flux<University>
}
All in separate files regarding their names.
I am getting a problem with my service, cause it cannot find repository. Consider defining a bean of type 'demo.university.UniversityRepository' in your configuration. But my repository file with exact name and interface is directly there.
I've tried to mark my repository with Bean annotation, but I can't do so with interfaces. Also, #EnableJpaRepositories does not help.
P.S. I know, it seems like a duplicate, but I really didn't find an answer in previous questions.
My problem was in a wrong project dependencies. As I mentioned, I migrated from simple Web to WebFlux. But I didn't change my MongoDB dependency. It should be marked as a reactive explicitly even if ReactiveMongoRepository interface is found correctly.
implementation("org.springframework.boot:spring-boot-starter-data-mongodb-reactive:2.7.1")

How do I set and read properties in a SpringBoot application using Kotlin?

I'm a Java developer new to Kotlin and I'm trying to access values that I set in an application.yml file.
application.yml
q:
client:
apiUrl: https://app.abc.com/api/integr/v1.0
apiToken: abc
apiSecret: abc
authEmail: abc#example.com
sourceName: abc
This is my configuration class, which follows a similar pattern to Java.
#Component
#FeignClient(name = "q", url = "\${q.client.api-url}")
interface QClient {
#PostMapping("/user/get")
fun findUser(#RequestBody request: QRequest?):
QResponse<List<QUser?>?>
#PostMapping("/user/delete")
fun deleteUser(#RequestBody request: QRequest?): QResponse<DeleteResponse?>?
#Configuration
class QConfig {
#Value("\${q.client.apiToken}")
private val apiToken: String? = null
#Value("\${q.client.apiSecret}")
private val apiSecret: String? = null
#Value("\${q.client.authEmail}")
private val authEmail: String? = null
#Value("\${q.client.sourceName}")
private val sourceName: String? = null
fun createAuthRequest(): QAuth {
return QAuth(apiToken, apiSecret, authEmail, sourceName)
}
}
I don't want to assign null as default values for the instance variables, but Kotlin wants me to declare them like this to avoid null references.
I need to create an auth request and I'm calling the config class from the main class.
private fun generateRequest(email: String): QRequest {
val config = QClient.QConfig()
val auth = config.createAuthRequest()
return QRequest(auth, email)
}
But when debugging it just returns null values.
So after googling, I changed my approach and set all the key values into parameters of QConfig class like this:
#Configuration
class QConfig(
#Value("\${q.client.apiToken}") private val apiToken: String,
#Value("\${q.client.apiSecret}") private val apiSecret: String,
#Value("\${q.client.authEmail}") private val authEmail: String,
#Value("\${q.client.sourceName}") private val sourceName: String
) {
fun createAuthRequest(): QAuth {
return QAuth(apiToken, apiSecret, authEmail, sourceName)
}
}
The problem I faced here was it acts as a constructor and expects me to pass arguments while creating an instance for the QConfig class on the main class, which I wont have in the main class.
How can I get the values from the application.yml and access them as from instance variables?
You can use #ConfigurationProperties (ref)
#ConfigurationProperties("q.client")
#ConstructorBinding
data class ClientConfig(
val apiUrl: String, // variable name should be same as the one defined in application.yaml
val apiToken: String,
...other properties
)
#SpringBootApplication
#ConfigurationPropertiesScan
class SpringStackoverflowApplication {
#Autowired
private lateinit var clientConfig: ClientConfig
#EventListener(ApplicationReadyEvent::class)
fun doSomething() {
println("FOOBAR: $clientConfig")
}
}
fun main(args: Array<String>) {
runApplication<SpringStackoverflowApplication>(*args)
}
I solved this with Joffrey's reply, I used this format of config file
#Component
#Configuration
class QConfig {
#Value("\${q.client.apiToken}")
private val apiToken: String? = null
#Value("\${q.client.apiSecret}")
private val apiSecret: String? = null
#Value("\${q.client.authEmail}")
private val authEmail: String? = null
#Value("\${q.client.sourceName}")
private val sourceName: String? = null
fun createAuthRequest(): QAuth {
return QAuth(apiToken, apiSecret, authEmail, sourceName)
}
}
Then created the instance of QConfig like this on main class
#Autowired
val config = QConfig()
My bad, tried creating reference of class manually instead of using AutoWire. When started it pulled all the env variables passed on .yml file into the local variables.

Kotlin & Jackson: type error when specifying custom serialisation for a data class field

I have a Kotlin data class that is serialised to JSON in a Spring Boot project. I'd like to customise how date is formatted when serialising to JSON. The name of the field should be serialised using default rules. That expresses what I'd like to do:
class ZonedDateTimeSerialiser : JsonSerializer<ZonedDateTime>() {
#Throws(IOException::class)
fun serialize(value: ZonedDateTime, gen: JsonGenerator, serializers: SerializerProvider?) {
val parseDate: String? = value.withZoneSameInstant(ZoneId.of("Europe/Warsaw"))
.withZoneSameLocal(ZoneOffset.UTC)
.format(DateTimeFormatter.ISO_DATE_TIME)
gen.writeString(parseDate)
}
}
data class OrderNotesRequest(
#JsonSerialize(using = ZonedDateTimeSerialiser::class)
val date: ZonedDateTime = ZonedDateTime.now()
)
But I get a type error:
Type mismatch.
Required:
KClass<out (JsonSerializer<Any!>..JsonSerializer<*>?)>
Found:
KClass<ZonedDateTimeSerialiser>
I did try switching the parameter to annotation to contentUsing but the type error remained the same.
Following is working for me
object JacksonRun {
#JvmStatic
fun main(args: Array<String>) {
val objMapper = ObjectMapper().apply {
registerModule(KotlinModule())
}
val order = OrderNotesRequest()
println(objMapper.writeValueAsString(order))
}
}
data class OrderNotesRequest(
#JsonSerialize(using = ZonedDateTimeSerialiser::class)
val date: ZonedDateTime = ZonedDateTime.now()
)
class ZonedDateTimeSerialiser : JsonSerializer<ZonedDateTime>() {
#Throws(IOException::class)
override fun serialize(value: ZonedDateTime, gen: JsonGenerator, serializers: SerializerProvider?) {
val parseDate: String = value.withZoneSameInstant(ZoneId.of("Europe/Warsaw"))
.withZoneSameLocal(ZoneOffset.UTC)
.format(DateTimeFormatter.ISO_DATE_TIME)
gen.writeString(parseDate)
}
}
build.gradle.kts:
dependencies {
implementation("com.fasterxml.jackson.core:jackson-core:2.13.2")
implementation("com.fasterxml.jackson.core:jackson-annotations:2.13.2")
implementation("com.fasterxml.jackson.core:jackson-databind:2.13.2")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.13.0")
}
Gives me output:
{"date":"2022-03-21T10:29:19.381498Z"}
Do make sure you have the correct import for JsonSerializer
import com.fasterxml.jackson.databind.JsonSerializer
and add override marker to serialize method

Spring Boot REST API - Deserialise a query attribute with square braces with Jackson

i am calling a legacy rest endpoint. There i have to deserialise a query parameter in a rest endpoint with square braces author[name].
Question: Is it possible to deserialise the attribute author name into the AuthorDto(spring boot + kotlin)?
import com.fasterxml.jackson.annotation.JsonAutoDetect
import com.fasterxml.jackson.annotation.JsonProperty
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RestController
#RestController
class AuthorController {
#GetMapping("/author/{id}")
fun getAuthorFromLegacyApi(
#PathVariable("id") id: Long,
authorDto: AuthorDto?
) = ResponseEntity.ok(authorDto)
}
#JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
data class AuthorDto(
#RequestParam(name = "author[name]")
val name: String?,
// long list of different query parameters
val country: String? = null,
)
code: https://github.com/nusmanov/spring-boot-validation-hibernate/blob/main/src/main/kotlin/com/example/bookvalidation/author/AuthorController.kt
to test: GET http://localhost:8080/author/5?author[name]=Joe
there is a junit test see above
Yes, it is possible, if you annotate authorDto with #RequestParam("author[name]"):
#GetMapping("/author/{id}")
fun getAuthorFromLegacyApi(#PathVariable("id") id: Long, #RequestParam("author[name]") authorDto: AuthorDto) = ResponseEntity.ok(authorDto.name)
How about using the map.
#GetMapping("/author/{id}")
fun getAuthorFromLegacyApi(#PathVariable("id") id: Long, #RequestParam authorDto: Map<String, String>): String {
return "Hello $id ${authorDto["author[name]"]} ${authorDto["currency"]}"
}
I tried with author/5?author%5Bname%5D=Tom&currency=INR got the response of Hello 5 Tom INR

Unit test POST API in spring-boot + kotlin + Junit

I'm pretty new to spring boot and kotlin. I've started with one basic app from net and writing unit test, but I'm getting following error:
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.IllegalStateException: articleRepository.save(article) must not be null
Let me show you the code: Entity Class
#Entity
data class Article (
#Id #GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long = 0,
#get: NotBlank
val title: String = "",
#get: NotBlank
val content: String = ""
)
controller:
#PostMapping("/articles")
fun createNewArticle(#Valid #RequestBody article: Article) : Article {
return articleRepository.save(article)
}
Repository:
#Repository
interface ArticleRepository : JpaRepository<Article, Long>
Test File:
RunWith(SpringRunner::class)
#SpringBootTest
class KotlinDemoApplicationTests {
lateinit var mvc: MockMvc
#InjectMocks
lateinit var controller: ArticleController
#Mock
lateinit var respository: ArticleRepository
#Before
fun setup() {
MockitoAnnotations.initMocks(this)
mvc = MockMvcBuilders.standaloneSetup(controller).setMessageConverters(MappingJackson2HttpMessageConverter()).build()
}
#Test
fun createBlog() {
var article = Article(1, "Test", "Test Content")
var jsonData = jacksonObjectMapper().writeValueAsString(article)
mvc.perform(MockMvcRequestBuilders.post("/api/articles/").contentType(MediaType.APPLICATION_JSON).content(jsonData))
.andExpect(MockMvcResultMatchers.status().isOk)
.andDo(MockMvcResultHandlers.print())
.andReturn()
}
}
When I'm running this test file, getting error mentioned above.
Please help me with this.
The problem is your ArticleRepository mock.
While you correctly inject it into your Controller, you're not specifiying what a call to save should return. It therefore returns null, which is not allowed in Kotin because you specified it as non-optional.
Either you allow your controller's createNewArticle to return null, by adding a ?, that is changing its signature to
fun createNewArticle(#Valid #RequestBody article: Article) : Article? {...}
Or you set-up the mock so that it does not return null, but an article.
#Before
fun setup() {
MockitoAnnotations.initMocks(this)
...
`when`(respository.save(any())
.thenReturn(Article()) // creates a new article
}
(Alternatively, there's also Mockito's returnsFirstArg() in case you don't want to invoke the construtor.)
Note that using any() in this case will only work if you're using mockito-kotlin
If you don't want to use it, check this answer

Resources