ktor - is there a way to serialize sub-template Template<FlowContent> to txt/html? - ktor

I am using HTML DSL as the templating engine in my ktor project.
I am trying to send one of the sub-templates Template as a text response (I do not want to send the full HtmlTemplate).
So far I have got below - to make it work I have to wrap my TestTemplate in another div:
fun Route.test() {
get("/test") {
call.respondText(
buildString {
appendHTML().div {
insert(TestTemplate(), TemplatePlaceholder())
}
}
)
}
}
This gives me following response (the inner div is my TestTemplate):
<div>
<div>this is the element I want to get, without the outer element</div>
</div>
What I would like to get is just the TestTemplate:
<div>this is the element I want to get, without the outer element</div>
Is there a way to achieve it in ktor using the HTML DSL?

You can make a class for the template with an HTMLStreamBuilder<StringBuilder> type parameter instead of FlowContent to build a final HTML string without any tags (FlowContent inherits Tag interface).
import io.ktor.application.*
import io.ktor.html.*
import io.ktor.response.*
import io.ktor.routing.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import kotlinx.html.*
import kotlinx.html.stream.HTMLStreamBuilder
suspend fun main() {
embeddedServer(Netty, port = 3333) {
routing {
get("/test") {
val builder = StringBuilder()
val html: HTMLStreamBuilder<StringBuilder> = HTMLStreamBuilder(builder, prettyPrint = true, xhtmlCompatible = false)
html.insert(TestTemplate(), TemplatePlaceholder())
call.respondText(html.finalize().toString())
}
}
}.start()
}
class TestTemplate : Template<HTMLStreamBuilder<StringBuilder>> {
val articleTitle = Placeholder<FlowContent>()
val articleText = Placeholder<FlowContent>()
override fun HTMLStreamBuilder<StringBuilder>.apply() {
article {
h2 {
insert(articleTitle)
}
p {
insert(articleText)
}
}
}
}

Related

If and how can I install a ktor plugin locally within a route for GET but not for POST?

For a ktor (2.0.3) application (kotlin 1.7.10) I want to have two endpoints on the same route (/feedback) but with different http methods, one GET and one POST.
So far no problem.
Now I would like to install an AuthorizationPlugin on only one of them.
I know how to install a plugin for a specific route only, but is it also possible to separately install it for different http methods on the same route?
So far I could not figure out a solution that does not require me to either introduce different routes (e.g. /feedback/read, /feedback/new) or handle the authorization check within the GET and POST callbacks directly.
The following is a reduced code containing two tests demonstrating the problem.
package some.example.package
import io.ktor.client.request.*
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.auth.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import io.ktor.server.testing.*
import org.apache.http.auth.AuthenticationException
import kotlin.test.Test
import kotlin.test.assertEquals
internal enum class AuthRole {
Admin, User
}
#kotlinx.serialization.Serializable
internal data class AuthUserSession(val username: String, val roles: Set<AuthRole> = setOf()) : Principal
const val authName = "form-auth"
const val usernameFormField = "username"
const val passwordFormField = "password"
/**
* Plugin Implementation
*/
internal val AuthorizationPlugin = createRouteScopedPlugin(
name = "AuthorizationPlugin",
createConfiguration = ::RoleBaseConfiguration
) {
pluginConfig.apply {
on(AuthenticationChecked) { call ->
val principal =
call.authentication.principal<AuthUserSession>() ?: throw Exception("Missing principal")
val userRoles = principal.roles
val denyReasons = mutableListOf<String>()
roles?.let {
if (roles!!.none { it in userRoles }) {
denyReasons += "Principal $principal has none of the sufficient role(s) ${
roles!!.joinToString(
" or "
)
}"
}
}
if (denyReasons.isNotEmpty()) {
val message = denyReasons.joinToString(". ")
throw Exception(message)
}
}
}
}
internal class RoleBaseConfiguration (
var roles: Set<AuthRole>? = null,
)
/**
* Server setup
*/
internal fun Application.setupConfig() {
install(Authentication) {
form(authName) {
userParamName = usernameFormField
passwordParamName = passwordFormField
challenge {
throw AuthenticationException()
}
validate { cred: UserPasswordCredential ->
if (cred.name == AuthRole.Admin.name) {
AuthUserSession(username = "admin", roles = setOf(AuthRole.Admin))
} else {
AuthUserSession(username = "user", roles = setOf(AuthRole.User))
}
}
}
}
routing {
route("feedback") {
authenticate(authName) {
post {
call.respond(HttpStatusCode.Created, "Submitting feedback")
}
install(AuthorizationPlugin) {
roles = setOf(AuthRole.Admin)
}
get {
call.respond(HttpStatusCode.OK, "Getting feedback")
}
}
}
}
}
/**
* Tests
*/
internal class PluginIssueTest {
/**
* For a valid solution this test should succeed.
*/
#Test
fun testGiveFeedback() = testApplication {
application {
setupConfig()
}
client.post("/feedback") {
header(HttpHeaders.ContentType, ContentType.Application.FormUrlEncoded.toString())
setBody(
listOf(
usernameFormField to AuthRole.User.name,
passwordFormField to "mypassword"
).formUrlEncode()
)
}.apply {
assertEquals(HttpStatusCode.Created, status)
}
}
/**
* For this test the plugin is successfully called and required role is checked.
*/
#Test
fun testReadFeedback() = testApplication {
application {
setupConfig()
}
client.get("/feedback") {
header(HttpHeaders.ContentType, ContentType.Application.FormUrlEncoded.toString())
setBody(
listOf(
usernameFormField to AuthRole.Admin.name,
passwordFormField to "mypassword"
).formUrlEncode()
)
}.apply {
assertEquals(HttpStatusCode.OK, status)
}
}
}
I made most of the things internal so they would not interfere with the implementations for my actual application. It should not have any influence on the tests.
Suggestions are highly appreciated.
If I forgot some important information please let me know.
I took a closer look at the implementation of authenticate and found a solution there.
It uses the createChild of the Route class with a custom RouteSelector that always evaluates to a "transparent" quality, to define a specific route for the authentication.
Since I currently only need a single RouteSelector instance I simplified it to be an object instead of a class.
By adding the following implementation to my code...
fun Route.authorize(
roles: Set<AuthRole>,
build: Route.() -> Unit
): Route {
val authenticatedRoute = createChild(AuthorizationRouteSelector)
authenticatedRoute.install(AuthorizationPlugin) {
this.roles = roles
}
authenticatedRoute.build()
return authenticatedRoute
}
object AuthorizationRouteSelector : RouteSelector() {
override fun evaluate(context: RoutingResolveContext, segmentIndex: Int): RouteSelectorEvaluation {
return RouteSelectorEvaluation.Transparent
}
override fun toString(): String = "(authorize \"default\" )"
}
...I was able to use my authorization plugin as follows:
routing {
route("feedback") {
authenticate(authName) {
post {
call.respond(HttpStatusCode.Created, "Submitting feedback")
}
authorize(setOf(AuthRole.Admin)) {
get {
call.respond(HttpStatusCode.OK, "Getting feedback")
}
}
}
}
}
making both tests succeed.

Setters don't modify two-dimensional array

Kotlin. I want a button to display values from a two-dimensional ArrayList, and a second button to modify one of them. But the setters don't modify the two-dimensional ArrayList. We can see the values with the first button, and after modifying the values at index 2 (third) with the second button, the values don't change:
model.get(2).setDateStrs("03/03/20")
model.get(2).setHourStrs("10:27")
What's wrong?
ReModel.kt file:
package com.example.updatearraylist
class ReModel {
var dateStr:String = "12/31/2029"
var hourStr: String = "00:00"
fun getDateStrs(): String {
return dateStr
}
fun setDateStrs(dateStr: String) {
this.dateStr = dateStr
}
fun getHourStrs(): String {
return hourStr
}
fun setHourStrs(hourStr: String) {
this.hourStr = hourStr
}
}
MainActivity.kt file:
package com.example.updatearraylist
import android.R.attr
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.widget.Button
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*
import java.lang.reflect.Array.get
import java.util.*
import kotlin.collections.ArrayList
class MainActivity : AppCompatActivity() {
private var displayValueBtn: Button? = null
private var changeValueBtn: Button? = null
val model: ArrayList<ReModel>
get() {
val list = ArrayList<ReModel>()
for (i in 0..7) {
val model = ReModel()
model.setDateStrs("01/16/2020")
model.setHourStrs("01:08")
list.add(model)
}
return list
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
displayValueBtn =findViewById<Button>(R.id.displayValueBtn)
changeValueBtn=findViewById<Button>(R.id.changeValueBtn)
displayValueBtn!!.setOnClickListener {
for(i in 0..7){
Toast.makeText(this, "Value position "+i+" "+model.get(i).getDateStrs()+" "+
model.get(i).getHourStrs()
,Toast.LENGTH_SHORT).show()
}
}
changeValueBtn!!.setOnClickListener {
model.get(2).setDateStrs("03/03/20")
model.get(2).setHourStrs("10:27")
Toast.makeText(this,"List Modified",Toast.LENGTH_LONG).show()
}
}
}
The custom getter on model will be executed each time model is accessed so any changes to the array are overwritten. If you want to verify that use a single println in the custom getter and whatever you print will display multiple times.
By "custom getter" I mean the get() on model and the associated block of code.
One solution is to use lazy initialization instead of a custom getter so that model is initialized only once. Here's how that would look:
val model: ArrayList<ReModel> by lazy {
val list = ArrayList<ReModel>()
for (i in 0..7) {
val model = ReModel()
model.setDateStrs("01/16/2020")
model.setHourStrs("01:08")
list.add(model)
}
list
}
Note that the last line with just list on it returns the value of list. return is not allowed there.

I can't generate a class using Kotlin processor

I'm implementing a processor to generate kotlin code using custom annotations. The problem is that I cannot find a way to relate the annotation to the field it was declared for, and I cannot find a way to understand if a field is of a nullable type. The processor doesn't succeed to generate the code because the getAnnotationsByType doesn't return the annotations for the current field (the list it's empty). Not even the order is good, fields are passed first and the annotations after all the fields.
package it.kfi.xml.binding.processor
import com.google.auto.service.AutoService
import com.squareup.kotlinpoet.*
import it.kfi.xml.binding.annotations.XmlClass
import it.kfi.xml.binding.annotations.XmlProperty
import java.io.File
import java.lang.reflect.Type
import javax.annotation.Nullable
import javax.annotation.processing.AbstractProcessor
import javax.annotation.processing.Processor
import javax.annotation.processing.RoundEnvironment
import javax.lang.model.SourceVersion
import javax.lang.model.element.Element
import javax.lang.model.element.ElementKind
import javax.lang.model.element.TypeElement
import javax.lang.model.element.VariableElement
import javax.lang.model.type.NullType
import javax.lang.model.type.TypeMirror
import javax.print.DocFlavor
import javax.tools.Diagnostic
import kotlin.reflect.KClass
import kotlin.reflect.full.createType
#AutoService(Processor::class)
class XmlBinder : AbstractProcessor() {
companion object {
const val KAPT_KOTLIN_GENERATED_OPTION_NAME = "kapt.kotlin.generated"
}
override fun getSupportedAnnotationTypes(): MutableSet<String> {
return mutableSetOf(XmlClass::class.java.name)
}
override fun getSupportedSourceVersion(): SourceVersion = SourceVersion.latest()
override fun process(annotations: MutableSet<out TypeElement>?, roundEnv: RoundEnvironment): Boolean {
roundEnv.getElementsAnnotatedWith(XmlClass::class.java)
.forEach {
if (it.kind != ElementKind.CLASS) {
processingEnv.messager.printMessage(Diagnostic.Kind.ERROR, "Only classes can be annotated")
return true
}
processClass(it)
}
return false
}
private fun processClass(element: Element) {
val className = element.simpleName.toString() + "Model"
val packageName = processingEnv.elementUtils.getPackageOf(element).toString()
val classBuilder = TypeSpec.classBuilder(className)
classBuilder.addModifiers(KModifier.PUBLIC)
val initFromXml = FunSpec.builder("initFromXml")
initFromXml.addModifiers(KModifier.PUBLIC)
initFromXml.addParameter(ParameterSpec.builder("xml", String::class).build())
val properties = element.enclosedElements
var x: Int = 1
//Look for elements annotated with XmlField and add those elements to the generated class
for (property in properties) {
val annotation = property.getAnnotationsByType(XmlProperty::class.java)
val v = 10
classBuilder.addProperty(PropertySpec.varBuilder(property.simpleName.toString(), String::class, KModifier.PUBLIC).initializer(v.toString()).build())
initFromXml.addStatement("this.${property.simpleName} = \"${v.toString()}\"")
}
classBuilder.addFunction(initFromXml.build())
val fileName = "kfi_generated_$className"
val file = FileSpec.builder(packageName, fileName).addType(classBuilder.build()).build()
val kaptKotlinGeneratedDir = processingEnv.options[KAPT_KOTLIN_GENERATED_OPTION_NAME]
file.writeTo(File(kaptKotlinGeneratedDir))
}
}
Can anyone help me found a way to relate annotations to their fields or properties ?

How mock Kotlin extension function in interface?

I have an extension function for interface like the following:
import javax.jms.ConnectionFactory
fun ConnectionFactory.foo() {
println("do some stuff")
}
How can I mock the function foo?
Please note, I have seen approaches for classes and objects in http://mockk.io/#extension-functions, but it does not work. I have tried this one:
import io.mockk.classMockk
import io.mockk.every
import org.junit.Test
import javax.jms.ConnectionFactory
class ExtensionFunctionTest {
#Test
fun mockExtensionFunction() {
val connectionFactory = classMockk(ConnectionFactory::class)
every { connectionFactory.foo() } returns println("do other stuff")
connectionFactory.foo()
}
}
It throws exception:
io.mockk.MockKException: Missing calls inside every { ... } block.
According to the documentation in case of module wide extension functions you need to staticMock "hidden" class created for an extension function.
Here is an example (assuming the file name is com/sample/extmockingtest/SampleTest.kt):
fun <T> Iterable<T>.foo(): String = "do some stuff"
class ExtensionFunctionTest {
#Test
fun mockExtensionFunction() {
val itMock = classMockk(Iterable::class);
staticMockk("com.sample.extmockingtest.SampleTestKt").use {
every {
itMock.foo()
} returns "do other stuff"
assertEquals("do other stuff", itMock.foo())
verify {
itMock.foo()
}
}
}
}

Why doesn't Kotlin let me use writeText extension?

I have a Kotlin class with a method, which creates some text and then I want to write it to a file:
import java.io.File
import java.util.*
import kotlin.io.*
class MyClass {
fun run() {
val result = html {
head {
title { +"Entry page" }
}
body {
h1 {
+"Map"
}
+"Some HTML code"
}
}
File("target/wdef/index.html").writeText(result)
}
}
I get an error - the writeText(result) is highlighted red and I get the error message Error:(26, 40) Kotlin: Unresolved reference: writeText.
How can I fix it?
A problem might be that you pass a wrong type to writeText. It requires String, but you pass an html building object HTML. Try to convert it with toString:
File("target/wdef/index.html").writeText(result.toString())