How to handle properly body params in #post request when using data classed with generics - kotlin

I'm trying to create endpoint for a post request:
#Singleton
#Controller("/v1")
class Addr() {
#Post("/setAddress")
fun set(#Body body: RpcRequest<Address>) {
println(body.params.newAddress)
}
}
Data:
data class Address(val newAddress: String)
#JsonIgnoreProperties(ignoreUnknown = true)
data class RpcRequest<T>(
val method: String,
val params: T
)
But unfortunately, error occured:
java.util.LinkedHashMap cannot be cast to com.project.location.data.Address
Seems like Micronaut couldn't resolve my Address data class inside RpcRequest ?

Related

How do we use data classes in kotlin to map to AWS Lambda?

I have a simple data class as below:
data class AwsProxyRequest(var key1: String? = null, var key2: String? = null)
I am using API GATEWAY + Lambda proxy integration which invokes a lambda function as below:
class Handler: RequestHandler<AwsProxyRequest, AwsProxyResponse> {
override fun handleRequest(request: AwsProxyRequest, context: Context): AwsProxyResponse {
print(request.toString())
return AwsProxyResponse(body = "parsing error", statusCode = 500)
}
}
I am sending the following body:
{
"key1": "A",
"key2": "B"
}
When I try to invoke the end point, I recieve the following error from lambda:
Caused by: java.io.UncheckedIOException: com.fasterxml.jackson.databind.JsonMappingException: No suitable constructor found for type [simple type, class com.disqo.metering.behavior.api.collector.AwsProxyRequest]: can not instantiate from JSON object (missing default constructor or creator, or perhaps need to add/enable type information?)
It seems that jackson is not able to serialize kotlin data class. Any ideas how we can achieve this using data classes ?

Kotlin Gson toJson returns double quoted string

I am trying to encode JSON object from Kotlin object:
class RequestData(val phone: String)
...
val requestJson = Gson().toJson(RequestData("79008007060"))
After encoding I am getting quoted string
"{\"phoneNumber\":\"\"}"
instead
{"phoneNumber":""}
Could you tell me why does it happen and how to fix it?
I found a solution. I made mistake in other place. I tried to send POST JSON request to server with Retrofit and encoded object to JSON-string before put it to #Body:
interface AuthService {
#POST("requestPinCode")
fun requestPinCode(#Body body: String): Observable<ApiResult>
}
...
data class RequestData(val phone: String)
...
val requestJson = Gson().toJson(RequestData("79008007060"))
authService.requestPinCode(requestJson)
But right way is to put not encoded object to #Body
interface AuthService {
#POST("requestPinCode")
fun requestPinCode(#Body body: RequestData): Observable<ApiResult>
}
...
data class RequestData(val phone: String)
...
authService.requestPinCode(RequestData("79008007060"))

Client posting multipart form data

I'm trying to create a client which I use to test my controller
The controller
#Secured(SecurityRule.IS_AUTHENTICATED)
#Controller
class InjuryController(private val userService: UserService, private val injuryService: InjuryService) {
...
#Consumes(MediaType.MULTIPART_FORM_DATA)
#Post("/injuries/{id}/images")
fun postImage(id: Long, file: CompletedFileUpload, principal: Principal): HttpResponse<*>? {
...
return HttpResponse.ok(imageReference)
}
...
}
The client
#Client("/")
interface InjuryClient {
#Post("/injuries/{id}/images", produces = [MULTIPART_FORM_DATA])
fun postImage(id: Long, body: MultipartBody, #Header authorization: String): ImageReference
}
The test
#Test
fun `Post an image an injury`() {
// Given
val description = "description"
val occurredAt = LocalDateTime.now()
val id = createInjury(description, occurredAt).id
val toWrite = "test file"
val file = File.createTempFile("data", ".txt")
FileWriter(file).apply {
write(toWrite)
close()
}
val requestBody = MultipartBody.builder()
.addPart("data",
file.name,
MediaType.TEXT_PLAIN_TYPE,
file
).build()
// When
val response = injuryClient.postImage(id, requestBody, authorization)
// Then
assertEquals("$id:${file.name}", response.key)
}
The error
The type java.util.LinkedHashMap is not a supported type for a multipart request body
io.micronaut.http.multipart.MultipartException: The type java.util.LinkedHashMap is not a supported type for a multipart request body
at io.micronaut.http.client.DefaultHttpClient.buildMultipartRequest(DefaultHttpClient.java:2063)
at io.micronaut.http.client.DefaultHttpClient.buildNettyRequest(DefaultHttpClient.java:1480)
at io.micronaut.http.client.DefaultHttpClient.sendRequestThroughChannel(DefaultHttpClient.java:1599)
at io.micronaut.http.client.DefaultHttpClient.lambda$null$27(DefaultHttpClient.java:1035)
at io.netty.util.concurrent.DefaultPromise.notifyListener0(DefaultPromise.java:577)
at io.netty.util.concurrent.DefaultPromise.notifyListeners0(DefaultPromise.java:570)
at io.netty.util.concurrent.DefaultPromise.notifyListenersNow(DefaultPromise.java:549)
at io.netty.util.concurrent.DefaultPromise.notifyListeners(DefaultPromise.java:490)
at io.netty.util.concurrent.DefaultPromise.setValue0(DefaultPromise.java:615)
at io.netty.util.concurrent.DefaultPromise.setSuccess0(DefaultPromise.java:604)
at io.netty.util.concurrent.DefaultPromise.trySuccess(DefaultPromise.java:104)
at io.netty.channel.DefaultChannelPromise.trySuccess(DefaultChannelPromise.java:84)
at io.netty.channel.nio.AbstractNioChannel$AbstractNioUnsafe.fulfillConnectPromise(AbstractNioChannel.java:300)
at io.netty.channel.nio.AbstractNioChannel$AbstractNioUnsafe.finishConnect(AbstractNioChannel.java:335)
at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:688)
at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:635)
at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:552)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:514)
at io.netty.util.concurrent.SingleThreadEventExecutor$6.run(SingleThreadEventExecutor.java:1044)
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.base/java.lang.Thread.run(Thread.java:834)
Any clue about how to get rid of that error?
The client assumes method arguments are members of the body instead of each one representing the entire body. You can achieve the desired behavior by annotating the body argument in the client with #Body

How to create a default body for a Retrofit call directly in the interface?

I would like to add a default body to a single Retrofit call inside the interface I made.
Let's say I have a Retrofit interface such as:
import retrofit2.Call
import retrofit2.http.*
interface ExampleAPI {
#POST
fun makeRequest(): Call<SomeResponse>
}
And I would like to add a default body to the request with fields such as:
param_one: j32n4n4jt
param_two: k23n45k43t
I am aware I can wrap the generated function and inject the body via:
import retrofit2.Call
import retrofit2.http.*
interface ExampleAPI {
#POST
fun makeRequest(#Body body: Map<String, String>): Call<SomeResponse>
}
or I can make a if check in an interceptor.
However, is it possible to implement this directly in the interface, and if so, how?
Try this
Generic Request:
open class CommonRequest(
#Ignore
#SerializedName("param_one") val param_one: String = "j32n4n4jt",
#Ignore
#SerializedName("param_two") val param_two: String = "j32n4n4jt"
)
Actual Request:
data class Request(
#SerializedName("name") val name: String = "Andrew",
) : CommonRequest()
Usage:
interface ExampleAPI {
#POST
fun makeRequest(#Body request: Request): Call<SomeResponse>
}

Add attribute to XML without POJO modification

I need to supply shared secret attribute to XML, so I decided to add it without exposing it to my API.
#JacksonXmlRootElement(localName = "Request")
data class TestRequest(#JacksonXmlText val request: String)
Here is example POJO, after serializer it looks like
<Request>text</Request>
I need to add attribute to it, like
<Request secret="foobar">text</Request>
I looked to Jackson API and it looks like I need to create custom serializer for root, so
class SessionModule: SimpleModule("Test serializer", PackageVersion.VERSION) {
override fun setupModule(context: SetupContext) {
context.addBeanSerializerModifier(object : XmlBeanSerializerModifier() {
override fun modifySerializer(config: SerializationConfig, beanDesc: BeanDescription, serializer: JsonSerializer<*>): JsonSerializer<*> {
val modifiedSerializer = super.modifySerializer(config, beanDesc, serializer)
if (modifiedSerializer is XmlBeanSerializer) {
println("Registering custom serializer")
return SessionFieldSerializer(modifiedSerializer)
}
return modifiedSerializer
}
})
}
}
And my custom serializer that do nothing
class SessionFieldSerializer: XmlBeanSerializer {
constructor(src: BeanSerializerBase?) : super(src)
constructor(src: XmlBeanSerializerBase?, objectIdWriter: ObjectIdWriter?, filterId: Any?) : super(src, objectIdWriter, filterId)
constructor(src: XmlBeanSerializerBase?, objectIdWriter: ObjectIdWriter?) : super(src, objectIdWriter)
constructor(src: XmlBeanSerializerBase?, toIgnore: MutableSet<String>?) : super(src, toIgnore)
override fun serialize(bean: Any?, g: JsonGenerator?, provider: SerializerProvider?) {
TODO()
}
}
So, all it do is throw not-implemented exception, however even if SessionFieldSerializer() getting instantiated ( I see "Registering custom serializer" message), serialize function is not called.
Test code:
val mapper = XmlMapper()
mapper.registerModule(KotlinModule())
mapper.registerModule(SessionModule())
val request = TestRequest("Foobar")
val test = mapper.writeValueAsString(request)
println(test)
Am I missing something?