in ktor plugin "Serialization" don't work with plug in "FreeMaker" - ktor

ok I'm new to Ktor and I'm learning about it, one thing that I faced in this progress was this error :
kotlinx.serialization.SerializationException: Serializer for class 'FreeMarkerContent' is not found.
Mark the class as #Serializable or provide the serializer explicitly
and thats becouse in my code, Im using FreeMakcerContent and its trying to Serlialize it :
get {
call.respond(FreeMarkerContent("index.ftl", mapOf("articles" to articles)))
}
how can I fix this problem?

I faced the same problem and I fixed it by installing FreeMarker plugin before installing ContentNegotiation plugin.
install(FreeMarker) {
templateLoader = ClassTemplateLoader(this::class.java.classLoader, "templates")
}
install(ContentNegotiation) {
json()
}
routing {
get("/html-freemarker") {
call.respond(FreeMarkerContent("index.ftl", mapOf("name" to "Shalaga"), ""))
}
}

ok I think I found the solution, in the configuration of Serialization I did define the route that i need sterilization in it ( my Http Api route), something like this
fun Application.configureSerialization() {
routing {
route("api"){
install(ContentNegotiation) {
json()
}
}
}
}
so the Serialization will only work in this route

Related

Mocking internal function call in Kotlin

I am a complete beginner in terms of Kotlin and I am finding some issues while trying to test out a Ktor based application.
I have a file in my endpoints package localized at org.example.endpoints.hello
this file contains a fun Application.hello that implements an endpoint for my application.
This endpoint acts as a wrapper for another API, so inside that same file I have a
fun callOtherAPI(): ResponseContainer {
// networking stuff
return ResponseContainer(message: "some stuff")
}
This function gets called inside the Application's function routing implementation as such:
routing {
get("/hello") {
call.respond(callOtherAPI())
}
}
Now to the issue:
My test currently looks like this:
#Test
fun testHello() = testApplication {
application {
hello()
}
mockkStatic(::callOtherAPI)
every { callOtherAPI() } returns ResponseContainer("hello")
print(callOtherAPI()) // This actually returns the mocked response, which is what I want
client.get("/hello").apply {
val expected = ResponseContainer("hello")
val response = jacksonObjectMapper().readValue<ResponseContainer>(bodyAsText())
assertEquals(HttpStatusCode.OK, status)
assertEquals(expected.message, response.message) // This assert fails because the internal call to callOtherAPI() is not being mocked.
}
}
So the problem that I am facing is that while the mocked function is being mocked within the context of the test, it is not being mocked when called internally by the routing implementation.
Can someone point me to good documentation to figure this out, I've been at it for the past two hours to no avail :/
Thanks!
You can declare a parameter for the callOtherAPI function in the hello method. For the production and testing environment you will pass different functions in this case. Here is your code rewritten:
#Test
fun testHello() = testApplication {
application {
// hello(::callOtherAPI) this call will be for the production environment
hello { ResponseContainer("hello") }
}
client.get("/hello").apply {
assertEquals(HttpStatusCode.OK, status)
assertEquals("{\"message\":\"hello\"}", bodyAsText())
}
}
data class ResponseContainer(val message: String)
fun Application.hello(callOtherAPI: () -> ResponseContainer) {
install(ContentNegotiation) {
jackson()
}
routing {
get("/hello") {
call.respond(callOtherAPI())
}
}
}
fun callOtherAPI(): ResponseContainer {
// networking stuff
return ResponseContainer("some stuff")
}

ktor test faild with 'Response has already been sent'

I trying to create API with ktor.
Test for ktor api using JUnit5 and I need use the custom properties for database information in 'Application.conf' like below:
ktor {
deployment {
.....
}
application {
.....
}
database {
host = ${?DB_HOST}
user = ${?DB_USER}
pass = ${?DB_PASS}
}
}
I following official guidline for useing custom properties:
HoconApplicationConfig
private val testEnv = createTestEnvironment {
config = HoconApplicationConfig(ConfigFactory.load("application.conf"))
}
class MyTest {
#Test
fun Testing() {
withApplication(testEnv) {
handleRequest(HttpMethod.Get, "/foo").apply {
...
}
}
}
}
Succeed test when only one function in class, but defined second test function then test failed with Exception.
I want define multiple function to simplify the definition and checking in case of failure.
class MyTest {
#Test
fun Testing() {
withApplication(testEnv) {
handleRequest(HttpMethod.Get, "/foo").apply {
....
}
}
}
#Test
fun Testing2() {
withApplication(testEnv) {
handleRequest(HttpMethod.Get, "/foo/bar").apply {
...
}
}
}
}
io.ktor.server.engine.BaseApplicationResponse$ResponseAlreadySentException: Response has already been sent
I know all test will success using 'withTestApplication' but can't use the custom properties.
How can I use the custom properties, and define multiple test funcitons?
Environment:
macOS Big Sur 11.5
IntelliJ Ultimate 2021.1.3
kotlin 1.5.21
ktor 1.6.1
junit-jupiter 5.7.0
Sorry for bad English.
Best Regards.

Ktor is throwing a "Lost in ambiguity tie" error on GET requests

I have a project that was running on Kotlin 1.4.0 and Ktor 1.3.2, so I decided to try and upgrade my versions to Kotlin 1.4.30 and Ktor 1.5.1 (since I was also upgrading to Java 11 due to adoptopenJDK8 not having JavaFx).
When I finally fixed import issues and whatnot and ran my webserver, the first page opens correctly (it's mapped to route "/"), but the other GET requests throw a "Lost in ambiguity tie" error on the RoutingResolve class. Apparently when comparing the quality of registered selectors, the "/ping" one has the same value as "//", hence throwing the error. I have validated this behaviour Ktor 1.5.1 and 1.5.0. Versions 1.4.x throw coroutine errors.
My server configurations are declared as such:
fun serverConfiguration(controller: Controller): NettyApplicationEngine {
SocketRoutes.controller = controller
WebUIRoutes.controller = controller
return embeddedServer(Netty, 9090) {
install(ContentNegotiation) {
jackson {
enable(SerializationFeature.INDENT_OUTPUT)
dateFormat = DateFormat.getDateInstance()
deactivateDefaultTyping()
}
}
install(FreeMarker) {
templateLoader = ClassTemplateLoader(this::class.java.classLoader, "templates")
defaultEncoding = Charsets.UTF_8.toString()
}
install(CallLogging)
install(Sessions) {
cookie<UserPreferences>("preferences")
}
getRoutes(this)
}
}
My Routes.kt
fun getRoutes(pipeline: Application): Routing {
return Routing.install(pipeline) {
trace { application.log.trace(it.buildText()) }
static("/") {
resources("web-static")
}
home()
getTableBody()
ping()
}
}
And these routes themselves are on WebUIRoutes.kt
object WebUIRoutes {
lateinit var controller: Controller
fun Route.home() {
get("/") {
val session = call.sessions.get<UserPreferences>() ?: UserPreferences()
call.sessions.set(session)
val nrPages = calculateNrOfPages(session.nrElements)
call.response.cookies["nrElements"]?.copy(value = "$nrPages")
call.respondTemplate(
"showCodes.ftl", mapOf(
"total" to nrPages, "nrElements" to session.nrElements,
"validateOnInput" to session.validateOnInput
)
)
}
}
(...)
fun Route.ping() {
get("/ping") {
if (controller.ping())
call.respond(HttpStatusCode.OK)
else
call.respond(HttpStatusCode.NotFound)
}
}
}
So I have no idea what the hell is going on and I've found nothing online about migrating to newer versions of Ktor and related issues...
The problem is that the resources function adds a route with a wildcard under the / route and therefore the routing resolves it instead of the /ping route. You can just move the static function call to the end of Routing to make it work as you expect.

How to configure jackson-modules-java8 in Ktor

I am trying to configure jackson-modules-java8 with Ktor and Jackson but to no avail.
The module is added to gradle.build
dependencies {
...
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.12.0-rc2'
...
}
According to the Jackson docs I should do this:
ObjectMapper mapper = JsonMapper.builder()
.addModule(new JavaTimeModule())
.build();
But in Ktor I can only do this:
install(ContentNegotiation) {
jackson {
// `this` is the ObjectMapper
this.enable(SerializationFeature.INDENT_OUTPUT)
// what to do here?
}
}
According with the official example if you want to add a module you could use
registerModule
as this:
install(ContentNegotiation) {
jackson {
configure(SerializationFeature.INDENT_OUTPUT, true)
setDefaultPrettyPrinter(DefaultPrettyPrinter().apply {
indentArraysWith(DefaultPrettyPrinter.FixedSpaceIndenter.instance)
indentObjectsWith(DefaultIndenter(" ", "\n"))
})
registerModule(JavaTimeModule()) // support java.time.* types
}
}

Mono<ResponseEntity> ignored when using AWS SDK DynamoDB enhanced Kotlin

I am playing around with Spring WebFlux, AWS SDK 2.X, Kotlin, and DynamoDB Enhanced client. I want to return 201 with the Location header but I am getting a 200. It seems that the AWS SDK is messing my actual ResponseEntity
#RestController
#RequestMapping("/test")
class TestController(
private val dynamoDbAsyncTable: DynamoDbAsyncTable<TestEntity>
) {
#PostMapping
fun save(): Mono<ResponseEntity<Unit>> {
// This returns 201 as expected
// return Mono.just("someId").map { ResponseEntity.created(URI.create("/test").build() }
// This returns 200
return Mono.fromFuture(dynamoDbAsyncTable.putItem(TestEntity("someId"))).map { ResponseEntity.created(URI.create("/test")).build<Unit>() }
}
}
Am I missing something? Thanks in advance!