(Spring boot) How can I ignore some field from request body inside rest controller - spring-boot

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
)

Related

Spring Data Mongo with Koltin coroutines and #DocumentReference

I am using Spring Data MongoDB with Kotlin Coroutines to create a non-blocking server.
I have a document Deck:
#Document
data class Deck(
#Id val id: ObjectId = ObjectId.get(),
#DocumentReference(lazy = true) val author: User,
val title: String,
val desc: String,
#DocumentReference(lazy = true) val cards : Set<Card> = emptySet(),
#CreatedDate val createdAt: Instant = Instant.now(),
#LastModifiedDate val lastModifiedAt: Instant = Instant.now()
)
Deck has a reference to a User type in the author field:
#Document
data class User(
#Id val id: ObjectId = ObjectId.get(),
val name: String,
#Indexed(unique = true, name = "username_index") val username: String,
val password: String,
#CreatedDate val createdAt: Instant = Instant.now(),
#LastModifiedDate val lastModifiedAt: Instant = Instant.now(),
#DocumentReference(lazy = true) val decks: Set<Deck> = emptySet()
)
Here's my DeckRepository interface:
interface DeckRepository: CoroutineSortingRepository<Deck, String> {
}
The Deck documents are getting saved properly in the database with the author's ObjectId.
But when I try to fetch the deck with id, the repository method gives an error. It's not able to populate the author's reference.
override suspend fun getDeck(deckId: String): DeckResponseDTO {
log.info("Getting deck with id $deckId")
return deckRepository.findById(deckId)?.toDto() ?: throw Exception("Could not find deck with id $deckId")
}
Stacktrace snippets:
Failed to instantiate io.gitub.startswithzed.dex.model.Deck using constructor fun <init>(org.bson.types.ObjectId, io.gitub.startswithzed.dex.model.User, kotlin.String, kotlin.String, kotlin.collections.Set<io.gitub.startswithzed.dex.model.Card>, java.time.Instant, java.time.Instant): io.gitub.startswithzed.dex.model.Deck with arguments 63173407712ec0263535829a,null,Test title,Test Desc,null,2022-09-06T11:50:31.370Z,2022-09-06T11:50:31.370Z,16,null
at org.springframework.data.mapping.model.KotlinClassGeneratingEntityInstantiator$DefaultingKotlinClassInstantiatorAdapter.createInstance(KotlinClassGeneratingEntityInstantiator.java:215) ~[spring-data-commons-2.7.2.jar:2.7.2]
Caused by: java.lang.NullPointerException: Parameter specified as non-null is null: method io.gitub.startswithzed.dex.model.Deck.<init>, parameter author
at io.gitub.startswithzed.dex.model.Deck.<init>(Deck.kt) ~[main/:na]
at io.gitub.startswithzed.dex.model.Deck.<init>(Deck.kt:14) ~[main/:na]
I even verified the data by querying using lookup in the console. Somehow Spring Data is not able to query properly.
Thanks for the help!

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.

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.

Return different Dto using same repository method with Spring Boot repositories

I have two DTOs
One for a get method like "/pessoas/{id}"
And another one for "/pessoas/{id}/detalhes" where I am going to see more attributes of Pessoa.
My codes are in Kotlin.
My simple DTO:
interface PessoaDTO {
val idInstitucional: UUID?
val nome: String?
}
data class PessoaDTOImpl(override val idInstitucional: UUID?, override val nome: String?): PessoaDTO
My DTO with details:
interface PessoaDetalhesDTO {
val idInstitucional: UUID?
val nome: String?
val email: String?
val telefone: String?
val cpf: Long?
}
data class PessoaDetalhesDTOImpl(override val idInstitucional: UUID?, override val nome: String?, override val email: String?, override val telefone: String?, override val cpf: Long?): PessoaDetalhesDTO
I have a repository that will be accessed by my PessoaController. I was thinking about having two methods in my Repository, each one for a different DTO.
That's my Repository:
internal interface PessoaRepository : CrudRepository<Pessoa, Long>, JpaSpecificationExecutorWithProjection<Pessoa> {
fun findByIdInstitucional(idInstitucional: UUID): PessoaDTO?
fun findByIdInstitucional(idInstitucional: UUID): PessoaDetalhesDTO?
}
However, I can't have two functions with the same name in the Repository for different returned data types.
How can I deal with that without having to create another Repository for the detailed information of Pessoa?

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