Akka HTTP does not handle ValidationRejections, instead throws require java.lang.IllegalArgumentException: requirement failed - akka-http

I have the following test
"adding a nominee with empty fields " should {
"be rejected" in {
val nominee = NomineeReq("Saving", "", "***", "29-08-1984", "Friend", "***", Some("***"), "1234", "****", "Some state", Some("****"))
Post("/v1/nominees/", nominee)
.withHeaders(List(httpHeaders.Authorization(httpHeaders.OAuth2BearerToken("4abf9af4-1910-437c-8510-1c8d4222901b")))) ~> Route.seal(route) ~> check {
status should be(StatusCodes.BadRequest)
}
}
}
And the following service code
case class NomineeReq(
val account_type: String,
val account_subtype: String,
val name: String,
val dob: String,
val relationship: String,
val address1: String,
val address2: Option[String],
val pin_code: String,
val city: String,
val state: String,
val email: Option[String]){
require(account_type.nonEmpty, "account_type")
require(account_subtype.nonEmpty, "account_subtype =")
require(name.nonEmpty, "name.")
require(relationship.nonEmpty, "relationship")
require(address1.nonEmpty, "address1")
require(pin_code.nonEmpty, "pin_code")
require(city.nonEmpty, "city")
require(state.nonEmpty, "state")
require(email.nonEmpty, "email")
}
/**some code**/
(post & entity(as[NomineeReq])) { nominee =>
complete(setNominee(user.userId, nominee))
}
When I run it, I get the following error - "java.lang.IllegalArgumentException: requirement failed: account_subtype =
[info] at scala.Predef$.require(Predef.scala:224)"
As per [doc][1], this
[1]: http://doc.akka.io/docs/akka/2.4.3/scala/http/routing-dsl/case-class-extraction.html "doc" this should result in a ValidationRejections, which will be translated by the default rejection handler to a BadRequest

Related

Jackon JSON Adding deserializer module doesn't work, but annotation on class does

I am trying to implement a custom deserializer and when I try to install it on the ObjectMapper it never gets invoked, but when I use it directly as an annotation on the class, it does. Can someone explain why and how I can actually install it on the object mapper itself?
This is never invoked
val bug = ObjectMapper().registerModule(KotlinModule.Builder().build())
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.registerModule(SimpleModule().addDeserializer(Bug::class.java, BugDeserializer()))
.readValue(bugStream, Bug::class.java)
data class Bug(
#JsonProperty("rptno")
val id: Long,
#JsonProperty("status")
#JsonDeserialize(using = StatusDeserializer::class)
val status: Status,
#JsonProperty("reported_date")
val reportedDate:Instant,
#JsonProperty("updated_date")
val updatedDate: Instant,
// val pillar: String = "",
#JsonProperty("product_id")
#JsonDeserialize(using = ProductDeserializer::class)
val product: Product,
#JsonProperty("assignee")
val assignee: String,
// val serviceRequests: List<Long> = listOf(),
#JsonProperty("subject")
val title: String,
#JsonProperty("bug_type")
val type: String
)
But, this does:
val bug = ObjectMapper().registerModule(KotlinModule.Builder().build())
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.readValue(bugStream, Bug::class.java)
#JsonDeserialize(using = BugDeserializer::class)
data class Bug(
#JsonProperty("rptno")
val id: Long,
#JsonProperty("status")
#JsonDeserialize(using = StatusDeserializer::class)
val status: Status,
#JsonProperty("reported_date")
val reportedDate:Instant,
#JsonProperty("updated_date")
val updatedDate: Instant,
// val pillar: String = "",
#JsonProperty("product_id")
#JsonDeserialize(using = ProductDeserializer::class)
val product: Product,
#JsonProperty("assignee")
val assignee: String,
// val serviceRequests: List<Long> = listOf(),
#JsonProperty("subject")
val title: String,
#JsonProperty("bug_type")
val type: String
)
```kotlin

`when` statement with multiple arguments in Kotlin

data class Vehicle(val name: String, val category: String,val subCategory: String, val region: String)
fun main() {
val newVehicle = Vehicle("Mitsubishi","Saloon", "Mini", "Kenya")
val newVehicle2 = Vehicle("Mitsubishi 2","Saloon 2", "Mini 2", "Kenya 2")
when(newVehicle.name || newVehicle.region) {
"Mitsubishi 2" -> {
print("Is Mitsubishi 2")
}
"Kenya" -> {
print("Is Kenya")
}
}
}
Consider the code block above, is it possible to have multiple arguments to avoid repeating checks for the conditions for both Vehicle.name and Vehicle.region ? This fails with the error: Type mismatch: inferred type is String but Boolean was expected
You can always do the following, but to be honest, I am not sure a when condition is what you need for your use case:
val namesAndRegions = listOf(newVehicle.name, newVehicle.region)
when {
"Mitsubishi 2" in namesAndRegions -> print("Is Mitsubishi 2")
"Kenya" in namesAndRegions -> print("Is Kenya")
}

How to parse LinkedHashMap in moshi (kotlin)

I am trying to create a JSON adapter for the following json
{
"message": {
"affenpinscher": [],
"african": [],
"airedale": [],
"akita": [],
"appenzeller": [],
"australian": [
"shepherd"
]
},
"status": "success"
}
I have tried the following
#JsonClass(generateAdapter = true)
data class BreedList(
val message: HashMap<String,List<String>> = HashMap<String,List<String>>()
)
and
#JsonClass(generateAdapter = true)
data class BreedList(
val message: Breed
)
#JsonClass(generateAdapter = true)
data class Breed(
val breed: List<String>
)
But both scenarios give me the errors, is there a way to parse the following object, I need the key as well as the list from the response
There is no need to create a custom adapter.
To parse the JSON you posted:
data class Base (
#field:Json(name = "message")
val message : Message,
#field:Json(name = "status")
val status : String
)
data class Message (
#field:Json(name = "affenpinscher")
val affenpinscher : List<String>,
#field:Json(name = "african")
val african : List<String>,
#field:Json(name = "airedale")
val airedale : List<String>,
#field:Json(name = "akita")
val akita : List<String>,
#field:Json(name = "appenzeller")
val appenzeller : List<String>,
#field:Json(name = "australian")
val australian : List<String>
)
Note: instead of String you can use whatever data type you need or create custom classes like Message.

Make na abstraction to kafka consumer using ktor and kotlin

I'm creating a abstraction for consumer and producer Kafka, to avoid duplicate code all the time. So i created a lib using kotlin and gradle, named "kafka-commons", and put the following code:
For Kafka producer:
fun producer(
bootstrapServers: String,
idempotence: Boolean,
acks: Acks,
retries: Int,
requestPerConnection: Int,
compression: Compression,
linger: Int,
batchSize: BatchSize
): KafkaProducer<String, Any> {
val prop: HashMap<String, Any> = HashMap()
prop[BOOTSTRAP_SERVERS_CONFIG] = bootstrapServers
prop[KEY_SERIALIZER_CLASS_CONFIG] = StringSerializer::class.java.name
prop[VALUE_SERIALIZER_CLASS_CONFIG] = StringSerializer::class.java.name
prop[ENABLE_IDEMPOTENCE_CONFIG] = idempotence
prop[ACKS_CONFIG] = acks.value
prop[RETRIES_CONFIG] = retries
prop[MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION] = requestPerConnection
prop[COMPRESSION_TYPE_CONFIG] = compression.value
prop[LINGER_MS_CONFIG] = linger
prop[BATCH_SIZE_CONFIG] = batchSize.value
return KafkaProducer(prop)
}
suspend inline fun <reified K : Any, reified V : Any> KafkaProducer<K, V>.dispatch(record: ProducerRecord<K, V>) =
suspendCoroutine<RecordMetadata> { continuation ->
val callback = Callback { metadata, exception ->
if (metadata == null) {
continuation.resumeWithException(exception!!)
} else {
continuation.resume(metadata)
}
}
this.send(record, callback)
}
So, i created a default Command object with the following structure
data class Command(
val id: UUID,
val status: CommandStatus,
val message: Any
)
id: create a unique ID
status: crete a message status (can be: Open /Processing / Closed / Error)
message: an object from http reques (for example: If have an Post
For example: if have a "insert user: POST" with body :
{ "id": 1, "name" : "John", "lastName" : "Wick" }
so message will be this object, and so on.
And for create this command, i made this function:
suspend fun creatCommand(
topicName: String,
id: UUID,
commandStatus: CommandStatus,
request: Any,
bootstrapServers: String,
idempotence: Boolean,
acks: Acks,
retries: Int,
requestPerConnection: Int,
compression: Compression,
linger: Int,
batchSize: BatchSize
): Unit {
val producer = producer(
bootstrapServers,
idempotence,
acks,
retries,
requestPerConnection,
compression,
linger,
batchSize)
val command = toCommand(processStarted(id, commandStatus, request))
val record = ProducerRecord<String, Any>(topicName, id.toString(), command)
coroutineScope { launch { producer.dispatch(record) } }
}
So, I created other APIs and just call this function to create a producer that sends a command to kafka. Like:
fun Route.user(service: Service) =
route("/api/access") {
post("/test") {
call.respond(service.command(call.receive()))
}
}
>>>>>> other class <<<<<<<<
classService () {
fun command( all parameters) { creatCommand(all parameters)}
}
Sor far so good. All works great.
Now my problemas begin. I'm trying to create a consumer.
First i made this:
fun consumer(
bootstrapServers: String,
group: String,
autoCommit: Boolean,
offsetBehaviour: OffsetBehaviour,
pollMax: Int
): KafkaConsumer<String, Any> {
val prop: HashMap<String, Any> = HashMap()
prop[BOOTSTRAP_SERVERS_CONFIG] = bootstrapServers
prop[KEY_DESERIALIZER_CLASS_CONFIG] = StringDeserializer::class.java.name
prop[VALUE_DESERIALIZER_CLASS_CONFIG] = StringDeserializer::class.java.name
prop[GROUP_ID_CONFIG] = group
prop[AUTO_OFFSET_RESET_CONFIG] = offsetBehaviour
prop[ENABLE_AUTO_COMMIT_CONFIG] = autoCommit
prop[MAX_POLL_RECORDS_CONFIG] = pollMax
return KafkaConsumer(prop)
}
And after:
fun<T> recordingCommand(
command: Class<T>,
topic: String,
bootstrapServers: String,
group: String,
autoCommit: Boolean,
offsetBehaviour: OffsetBehaviour,
pollMax: Int
) {
val consumer = consumer(bootstrapServers, group, autoCommit, offsetBehaviour, pollMax)
consumer.subscribe(mutableListOf(topic))
while (true) {
val records = consumer.poll(Duration.ofMillis(100))
for (record in records) {
val om = ObjectMaper
om.readvalue(record.value(), command::class.java)
>>>> I GOT LOST HERE <<<<<<
}
}
}
What i need: creating a abstract consumer that record all data inside a Command.message() (only the message) in a database.
For example, i need to record the user above (id 1, john wick ) into a postgresql database.
So if i had a service with insert method, i can call it, passing insertmethod like:
service.insert(recordingCommand(all parameters)).
Anyone can help me with that?
If I understood correctly, you are having issues with JSON mapping into objects
val mapper = ObjectMaper
while (true) {
val records = consumer.poll(Duration.ofMillis(100))
for (record in records) {
val cmd = mapper.readvalue(record.value(), command::class.java)
// do things with cmd
}
}
Note: Kafka has its own JSON to POJO deserializer, and if you want to send data to a database, Kafka Connect is generally more fault-tolerant than your simple consume loop.

kotlin default value in data class is aways zero [duplicate]

This question already has answers here:
Gson Deserialization with Kotlin, Initializer block not called
(2 answers)
Closed 4 years ago.
I've a simple data class of User
data class User(#PrimaryKey(autoGenerate = true) val id: Long, val _id: String, val name: String, val about: String,
val phoneNumber: String, val token: String,
val lastLogin: String, val avatarUrl: String, #Embedded val location: Location,
val emailId: String, val gender: Boolean, val timestamp: Long = System.currentTimeMillis())
As you can see the last parameter is val timestamp: Long = System.currentTimeMillis()
the response comes from network using retrofit and parsed using GSON
timestamp does not comes in a response json it's just extra field I need to do some logic. The problem is that value is always 0. It should be the current time stamp
This will do the trick,
data class User(#PrimaryKey(autoGenerate = true) val id: Long, val _id: String, val name: String, val about: String,
val phoneNumber: String, val token: String,
val lastLogin: String, val avatarUrl: String, #Embedded val location: Location,
val emailId: String, val gender: Boolean) {
var timestamp: Long = System.currentTimeMillis()
get() = if(field > 0) field else {
field = System.currentTimeMillis()
field
}
}