Return client error if part of request body in HTTP POST is null in Spring Boot in Kotlin - spring-boot

Spring Boot + Kotlin:
When a required Boolean in request body is not present (or it is null), then it is mapped to false.
How should I improve validation so that client error is returned? I even tried #field:NonNull and #field:Valid but they don't help..
Here's an example:
data class MyRequestBody(
val value1: String,
val value2: Boolean
)
#PostMapping("/blabla")
fun someMethod(
#RequestBody requestBody: MyRequestBody,
): CompletableFuture<MyResponse> = unwrappedAsync {
...
}
For this request:
sample request body: { "value1": "abc", "value2": null }
MyRequestBody contains value1='abc', value2=false
In other words, how to I prevent this null -> false mapping and make it a client error instead?

Why don't you use the java validation with NotNull annotation?
data class MyRequestBody(
val value1: String,
#get:NotNull
val value2: Boolean
)
Even you can use the annotation with the non-null boolean type.

Related

#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

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 boot) How can I ignore some field from request body inside rest controller

Suppose I have signUp method inside rest controller class looks like this.
#PostMapping("/signup")
fun authenticateSignUp(#RequestBody command: RegisterUserCommand): CompletableFuture<String> {
return commandGateway.send<String>(command)
}
So it requires request body which is RegisterUserCommand.
data class RegisterUserCommand(
val userId: String,
val balance: BigDecimal,
val username: String,
private val email: String,
private val password: String
)
I want to ignore some fields like userId, balance so I can generate it later inside controller like this
#PostMapping("/signup")
fun authenticateSignUp(#RequestBody request: RegisterUserCommand): CompletableFuture<String> {
val command = request.copy(userId = ObjectId.get().toHexString(), balance = BigDecimal.ZERO)
return commandGateway.send<String>(command)
}
Are there any annotation to ignore this field so it won't return bad request even though I didn't put userId, balance within request body
As correctly pointed out by #flaxel, you can use ? from Kotlin and I believe add #JvmOverloads constructor to it as well.
But, in my opinion, using DTO is the best way to deal with that for sure.
The input of your Controller should not be the Command but just a DTO with the fields you are interested in. How you build your command with enhanced values should not be affected by it.
In this case, you would have something like this (also added #TargetAggregateIdentifier because probably you missed it):
data class RegisterUserCommand(
#TargetAggregateIdentifier
val userId: String,
val balance: BigDecimal,
val username: String,
private val email: String,
private val password: String
)
...
data class RegisterUserDto(
val username: String,
private val email: String,
private val password: String
)
...
#PostMapping("/signup")
fun authenticateSignUp(#RequestBody request: RegisterUserDto): CompletableFuture<String> {
val command = new RegisterUserCommand // build the command the way you want it
return commandGateway.send<String>(command)
}
I guess you can mark the variable as nullable with the ? operator. But I would suggest to use DTOs.
data class RegisterUserDto(
val username: String,
private val email: String,
private val password: String
)

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