How can I get this DRY? Hello All, right now I le...
# ktor
b
How can I get this DRY? Hello All, right now I learn a lot about Kotlin extension functions and Kotlin DSLs. When using the DSL for Ktor routes I came to a point where I don't know how to get my routes setup DRY. In the example below, is there any chance to avoid repeating the routes
underpants()
and
groot()
?
Copy code
class SomeApp {

    fun Application.main(testing: Boolean = false) {

        when {
            isDev -> {
                // For dev environment only
                routing {
                    groot()                    
                    underpants()
                }
            }
            isProd -> {
                // Do things only in prod
                routing {
                    authenticate {
                        groot()                    
                        underpants()                    
                    }
                }
            }
        }

        // Do things for all the environments

        install(ContentNegotiation) {
            gson {
                setPrettyPrinting()
            }
        }
    }
} 

fun Route.groot(
) {
    get("/api/groot") {
        val map: HashMap<Int, String> = hashMapOf(1 to "I", 2 to "am", 3 to "groot!")
        call.respond(map)
    }
}

:
:
m
I would simply go ahead and mock the authentication in development mode (therefore allow any credentials or even none in the authentication callback) and remove pattern matching in the application module
b
Thank you for having a look Marius! I am too much of a newbie to implement your solution. Yet, when I tried to figure out how to do this, it turned out that
Route.authenticate()
has a parameter
optional: Boolean = false
. So, thank you for pointing me into the right direction, I ended up with this:
Copy code
class SomeApp {
    fun Application.main(testing: Boolean = false) {
        var authOptional = testing

        when {
            isDev -> {
                // For dev environment only
                install(ContentNegotiation) {
                    gson {
                        setPrettyPrinting()
                    }
                }
                install(Authentication) {
                    basic {
                        realm = "devMode"
                        validate { credentials ->
                            UserIdPrincipal("developer")
                        }
                    }
                }
                authOptional = true
            }
            isProd -> {
                // Do things only in prod
            }
        }

        // Do things for all the environments
        routing {
            authenticate(optional = authOptional) {
                groot()
            }
        }
    }
}
In prod mode, feature
Authentication
is installed by some other application. In dev mode
Authentication
is provided by the basic auth implemented here. Actually, in dev mode it is never called, but authenticate() throws
MissingApplicationFeatureException
if no
Authentication
is present
👍 1
j
In terms of testing I would suggest to also use authentication on dev. These things will bite you in the arse if your setup is too different from prod, we for example use sms verification and on non-prod this is a fake code (so we can use fake phone numbers) but the whole authentication is still in place
even if it is fake authentication it allows you to properly test the system
m
exactly! thought the same as well.
b
I totally agree. What I am trying to achieve is, to split my platform into verticals. I want to keep frontend development simple by starting ktor in dev mode so that I can work on isolated frontend components without having to deal with authentication. The final application that integrates all verticals uses JWT auth. Maybe it's smarter to have JWT always in place instead, with some hard coded credentials that will be used for frontend development as well as for unit testing
e
Hey @Bertram Kirsch, you also can try something simple, like:
Copy code
fun Routing.myAuthenticate(isDev: Boolean, block: Routing.() -> Unit) {
    if (isDev) {
        authenticate {
            block()
        }
    } else {
        block()
    }
}
b
Thank you! I am starting to get a grip on the routing DSL ... second argument of
myAuthenticate()
is the Routing block passed.
Copy code
routing {
            myAuthenticate(isDev = true){
                groot()
            }
        }
Copy code
fun Routing.myAuthenticate(isDev: Boolean, block: Routing.() -> Unit) {
    if (isDev) {
        authenticate {
            this@myAuthenticate.block()
        }
    } else {
        this@myAuthenticate.block()
    }
}
There was one compile error:
Kotlin: Unresolved reference. None of the following candidates is applicable because of receiver type mismatch:
public abstract operator fun Routing.invoke(): Unit defined in kotlin.Function1
IDEA inserted
this@myAuthenticate
when I applied the proposed IDE fix.
e
Thanks for the notice! It looks like receiver conflict, possibly can be solved by making the function top-level