Spring wrongfully tries to cast my sealed class into a Collection. Why? - spring

I have a problem with Spring, kotlin, and sealed classes, and don't know where it comes from. It would seem Spring tries to cast my ErrorDto class into a collection, when it shouldn't have to cast it.
Context
I have a sealed class Response that wraps my responses.
sealed interface Response<T>
class ErrorResponse<T>(status: HttpStatus, message: String) : Response<T>,
ResponseEntity<ErrorDto>(ErrorDto(status.value(), message), status)
class SuccessResponse<T>(responseEntity: ResponseEntity<T>) : Response<T>, ResponseEntity<T>(responseEntity.body, responseEntity.headers, responseEntity.statusCode) {
constructor(body: T): this(ok(body))
}
class ErrorDto(val status: Int, #SerializedName("error") val message: String? = null)
All my requests return types are Response<AType>, where AType is a typical response type so that:
we have a ResponseEntity<AType> when the request succeeds,
and when the request encounters an error, it returns a ResponseEntity<ErrorDto>
The wrapper Response is here so that I can correctly type the responses to the REST requests.
Working example
This works correctly with custom types, and here is an example of request that works at runtime:
#GetMapping("/test1")
fun test1(): Response<MyCustomType> {
val response: Response<MyCustomType> = if (true) {
ErrorResponse(HttpStatus.CONFLICT, "test")
} else {
SuccessResponse(MyCustomType())
}
return response
}
The problem
And now, here's an example of what doesn't work at runtime (it compiles just fine):
#GetMapping("/test2")
fun test2(): Response<List<String>> {
val response: Response<List<String>> = if (true) {
ErrorResponse(HttpStatus.CONFLICT, "test")
} else {
SuccessResponse(listOf("toto"))
}
return response
}
The runtime error with this test2 is:
[org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: class ErrorDto cannot be cast to class java.util.Collection
Why?
It would seem that Spring tries to cast the type to a collection when the wrapped response type is a List (Response<List<String>> here), when it shouldn't do that.
Any idea where this problem comes from and why Spring tries to cast to a Collection when it shouldn't do that?
N.B.: As a workaround, I wrapped my List with MyCustomClass in order to go around the problem, but I would still like to understand the why.

Related

Why is my data being interpreted as a readable stream instead of a json object?

I'm building a simple REST API in Kotlin and front-end with React. I'm pretty new to Kotlin and Spring, but I've managed to get a simple service working with Postman. I'm able to return all objects in an SQL table using JpaRepository interface. When I make the call through Postman, my output is as expected (looks like a normal Json object).
However, when I make the call through a standard react fetch the body is produced as a ReadableStream {locked: false}. I did some research on readable streams and they appear to mostly be for images. I tried body.getReader() from https://developer.mozilla.org/en-US/docs/Web/API/Streams_API/Using_readable_streams, but that did not work either.
Here is my Kotlin code:
import org.springframework.web.bind.annotation.*
#RestController
#RequestMapping("/api/catalog")
class ArtworkController (
private val artworkService: ArtworkService
) {
#CrossOrigin
#GetMapping("")
#ResponseBody
fun getData(): MutableList<Artwork> {
// println(artworkService.getArt().javaClass.kotlin)
return artworkService.getArt()
}
}
#Service
class ArtworkService(
private val artworkRepository: ArtworkRepository
){
fun getArt(): MutableList<Artwork> {
return artworkRepository.findAll()
}
}
These are in separate files - just joined them here for brevity.
Any help would be greatly appreciated!
It turns out I was immediately returning a promise and had to set up an async function to handle the request.
const { isLoading, error, data } = useQuery("repoData",
async () => {
const data = await fetch("https://url_here")
.then(function(response) {
return response.json()
}
)
setData(data)
}
For context, this request is wrapped in react-query but works without it as well.

How to mock webclient in Kotlin and spring boot for unit tests with mockk framework?

I have the following piece of code in Kotlin (using WebFlux), which I wanna test:
fun checkUser(user: People.User?): Mono<Unit> =
if (user==null) {
Mono.empty()
} else {
webClient.get().uri {
uriBuilder -> uriBuilder
//... building a URI
}.retrieve().bodyToMono(UserValidationResponse::class.java)
.doOnError {
//log something
}.map {
if (!item.isUserValid()) {
throw InvalidUserException()
}
}
}
My unit test so far looks like this:
#Test
fun `Returns error when user is invalid`() {
val user = People.User("name", "lastname", "street", "zip code")
//when
StepVerifier.create(checkUser(user))
//then
.expectError(InvalidUserException::class.java)
.verify()
}
However when I run it, it throw the following error:
io.mockk.MockKException: no answer found for: WebClient(#1).get()
at io.mockk.impl.stub.MockKStub.defaultAnswer(MockKStub.kt:90)
at io.mockk.impl.stub.MockKStub.answer(MockKStub.kt:42)
at io.mockk.impl.recording.states.AnsweringState.call(AnsweringState.kt:16)
at io.mockk.impl.recording.CommonCallRecorder.call(CommonCallRecorder.kt:53)
at io.mockk.impl.stub.MockKStub.handleInvocation(MockKStub.kt:263)
at io.mockk.impl.instantiation.JvmMockFactoryHelper$mockHandler$1.invocation(JvmMockFactoryHelper.kt:25)
at io.mockk.proxy.jvm.advice.Interceptor.call(Interceptor.kt:20)
I guess the error occurs because I havent mocked WebClient(#1).get() but I am not sure how to mock it. So far I have tried:
every { webClient.get() } returns WebClient.RequestHeadersUriSpec
but it doesnt compile. The error says:
Classifier 'RequestHeadersUriSpec' does not have a companion object, and thus must be initialized here
Someone knows how I can mock WebClient(#1).get()? Thanks in advance
Basically you need something like this:
mock ResponseSpec - mock the body or error in whichever way you need for the respective test case
mock RequestHeadersUriSpec - let the retrieve() method return the ResponseSpec mock
mock WebClient - let the get() method return the RequestHeadersUriSpec mock
Here is a full example:
val response = mockk<WebClient.ResponseSpec>()
val spec = mockk<WebClient.RequestHeadersUriSpec<*>>()
val client = mockk<WebClient>()
every { response.bodyToMono(String::class.java) } returns Mono.just("Hello StackOverflow")
every { spec.retrieve() } returns response
every { client.get() } returns spec
println(client.get().retrieve().bodyToMono(String::class.java).block())
This will correctly print the Hello StackOverflow string.
Though it may be a "historical" question, I actually also had this problem recently.
Just as what Krause mentioned, the full call path of WebClient should be mocked. This means the method stream in every{} block should as the same as WebClient call. In your case, it may be something like
every{webClient.get().uri {???}.retrieve().bodyToMono(???)} returns Mono.just(...)
The next question is something about the error message io.mockk.MockKException: no answer found for: RequestBodyUriSpec(#3).uri(......). The key to the question is methods with parameters and without parameters are totally different things.
Thus, for target method, a uri(Function<UriBuilder, URI> uriFunction) is called(a lambda expression is used here to instead of Function interface). However, for mock method, a uri() method without any parameter is called. This is why the error message said , "no answer found for ...". Therefore, in order to match the mocked method, the code should be:
every{webClient.get().uri(any<java.util.function.Function<UriBuilder, URI>>()).retrieve().bodyToMono(???)} returns Mono.just(...)
Or, the any() method can be changed to the real URI which should be as the same as the target method.
Similarly, bodyToMono() should also be mocked with the correct parameter, which may be bodyToMono(any<ParameterizedTypeReference<*>>()).
Finally, the mock code may look like:
every{client.get()
.uri(any<java.util.function.Function<UriBuilder, URI>>())
.retrieve().bodyToMono(any<ParameterizedTypeReference<*>>())}
return Mono.just(...)

Calling a function of a spyk'd data class

I have a data class A with a function as follows:
data class A(val a: String) {
fun foo(b: String) = "$a, $b"
}
I attempt the following mock in my test:
fun `whatever`() {
val spy = spyk<A>()
every { spy.a } returns "Tree"
assertThat(spy.foo("Snake")).isEqualTo("Tree Snake")
}
When I run a test written like this it fails with a NullPointerException on the line fun foo... in the data class.
Am I doing anything wrong or is this a bug in MockK?
I have totally different results when I run your code. Firstly it complains that there is no default constructor.
Then I fixed it to use the non-default constructor and it prints "abc Snake"
val spy = spyk(A("abc"))
every { spy.a } returns "Tree"
println(spy.foo("Snake"))
There is a reason for that. Kotlin is accessing a property through a field in foo function. This seems to be an optimization.
MockK is not able to do anything about it right now. There is the following ticket to transform getfield call: https://github.com/mockk/mockk/issues/104

AWS Lex receives Invalid Response from lambda function - Can not construct instance of IntentResponse

Using Java8 in eclipse AWS SDK, I've created and uploaded a lambda function that is hooked in upon fulfillment of my lex intent.
Lambda has not problem receiving JSON request and parsing.
Then, I format a simple "Close" dialogAction response and send back to lex and receive the following error from the Test Bot page in the lex console:
An error has occurred: Received invalid response from Lambda:
Can not construct instance of IntentResponse:
no String-argument constructor/factory method to deserialize
from String value
('{"dialogAction
{"type":"Close","fulfillmentState":"Fulfilled","message":
{"contentType":"PlainText","content":"Thanks I got your info"}}}')
at [Source: "{\"dialogAction\":
{\"type\":\"Close\",\"fulfillmentState\":\"Fulfilled\",\"message\":
{\"contentType\":\"PlainText\",\"content\":\"Thanks I got your
info\"}}}";line: 1, column: 1]
It seems to have a problem right away with the format (line 1, column 1), but my JSON string looks ok to me. Before returning the output string in the handleRequest java function, I am writing the it to the Cloudwatch log and it writes as follows:
{
"dialogAction": {
"type": "Close",
"fulfillmentState": "Fulfilled",
"message": {
"contentType": "PlainText",
"content": "Thanks I got your info"
}
}
}
Things I've tried:
Removing the message element as it's not required
Adding in non-required properties like sessionAttributes,
responseCard, etc
removing the double quotes
replacing double quotes with single quotes
hardcoding json from sample response format message in documentation
Is there something hidden at the http headers level or is java8 doing something to the JSON that is not visible?
Not sure if this is because I'm using Java8 or not, but a return value of "String" from the RequestHandler class handleRequest method will not work.
Yes, String is an object, but the constructors on the Lex side are expecting an "Object". I was converting my lex response POJO to a String before returning it in the handleRequest method. That was my mistake.
I fixed it by changing the return type of the handleRequest method to be "Object" instead of "String".
public Object handleRequest(Object input, Context context)
instead of
public String handleRequest(Object input, Context context)
You also have to implement the
public class LambdaFunctionHandler implements RequestHandler<Object, Object>
not
public class LambdaFunctionHandler implements RequestHandler<Object, String>
This solved my issue.
In my case I was facing exactly the same issue and was able to fix it by creating specific response POJO type and using this POJO as the return type for 'handleRequest' method. E.g. BotResponse.java as follow:
public class BotResponse implements Serializable{
private static final long serialVersionUID = 1L;
public DialogAction dialogAction = new DialogAction();
public DialogAction getDialogAction() {
return dialogAction;
}
public void setDialogAction(DialogAction dialogAction) {
this.dialogAction = dialogAction;
}
}
Note, I have also added the 'implements Serializable' just to be on safer side. Probably it is an overkill.
Not sure why but for me returning a well formatted JSON String object did not worked even after changing the return type of 'handleRequest' method to 'Object'.
I know this is an old question however thought this might help some else
#Mattbob Solution dint fix my issue, However he is in the right path. Best approach is to use a Response object, a custom response object and make the lambda return the custom response object. So i went to the Documentation and created a custom object that looks Response format here
http://docs.aws.amazon.com/lex/latest/dg/lambda-input-response-format.html
At the time of answering question i couldnt find an object in SDK that matched the response Object so i had to recreate but if some one knows please comment below
Class xxxxx implements RequestHandler<Object, AccountResponse> {
#Override
public AccountResponse handleRequest(Object input, Context context) {
}
}
Lambda will look somewhat like this and just populate and return the object to match response structure and error goes away. Hope this helps.
Whenever we are returning the object to the bot from the backend make sure we need to pass content type along with content. But here we are passing wrong. So wE need to pass as like below. It is in Node.js
let message = {
contentType: "PlainText",
content: 'Testing bot'
};

SparkJava using Kotlin and WebSockets

I am trying out Kotlin with SparkJava, and having trouble implementing the WebSockets routes. I am trying to follow the WebSockets example available on the SparkJava website (http://sparkjava.com/tutorials/websocket-chat), and whilst I can get the OnWebSocketConnect and OnWebSocketMessage elements to work, the OnWebSocketClose is not picked up.
I have implemented this in Java to double check that it is not a browser issues, and the Java implementation works fine...so this appears to be something specific to the way Kotlin is interpreting the OnWebSocketClose annotation.
My code looks like the following
import spark.Spark.*
import org.eclipse.jetty.websocket.api.Session
import org.eclipse.jetty.websocket.api.annotations.*;
fun main(args: Array<String>) {
staticFileLocation("/public")
webSocket("/chat", WSHandler::class.java)
init()
}
#WebSocket
class WSHandler {
#OnWebSocketConnect
fun connected(session: Session) = println("session connected")
#OnWebSocketClose
fun closed(session: Session, statusCode: Int, reason: String) = println("closed sessions")
#OnWebSocketMessage
fun message(session: Session, message: String) = println("Got: $message")
}
The html / javascript etc are as per the tutorial on the SparkJava website.
There is an error during invocation of closed method deep inside org.eclipse.jetty.websocket.common.events.annotated.CallableMethod class with the following message:
Parameter specified as non-null is null: method
webchat.WSHandler.closed, parameter reason
It is related to Kotlin's nullability features and all works fine when you declare your method using signature below:
fun closed(session: Session, statusCode: Int, reason: String?)

Resources