main.io.github.seiko.precompose.RouteDefinition.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of precompose-ksp Show documentation
Show all versions of precompose-ksp Show documentation
A route compiler for PreCompose (KSP).
package io.github.seiko.precompose
import com.google.devtools.ksp.symbol.KSValueParameter
import com.squareup.kotlinpoet.FunSpec
import com.squareup.kotlinpoet.KModifier
import com.squareup.kotlinpoet.ParameterSpec
import com.squareup.kotlinpoet.PropertySpec
import com.squareup.kotlinpoet.Taggable
import com.squareup.kotlinpoet.TypeName
import com.squareup.kotlinpoet.TypeSpec
import com.squareup.kotlinpoet.asTypeName
private const val RouteDivider = "/"
internal interface RouteDefinition {
val name: String
val parent: RouteDefinition?
fun generateRoute(): Taggable
}
internal fun RouteDefinition.parents(): List {
val list = arrayListOf()
var p = parent
while (p != null) {
list.add(0, p)
p = p.parent
}
return list
}
internal val RouteDefinition.parentPath
get() = parents()
.joinToString(RouteDivider) { it.name }
internal data class PrefixRouteDefinition(
val schema: String,
val child: NestedRouteDefinition,
val className: String,
) : RouteDefinition {
override val name: String
get() = if (schema.isEmpty()) "" else "$schema:$RouteDivider"
override val parent: RouteDefinition?
get() = null
init {
child.name = className
child.parent = this
}
override fun generateRoute(): Taggable {
return child.generateRoute()
}
}
internal data class ParameterRouteDefinition(
override val name: String,
override val parent: RouteDefinition?,
val childRoute: ArrayList = arrayListOf(),
) : RouteDefinition {
override fun generateRoute(): Taggable {
return TypeSpec.objectBuilder(name)
.addModifiers(KModifier.ACTUAL)
.apply {
childRoute.forEach {
if (it is FunctionRouteDefinition) {
val pathParams = it.parameters.filter { !it.parameter.type.resolve().isMarkedNullable }
val queryParams = it.parameters.filter { it.parameter.type.resolve().isMarkedNullable }
addProperty(
PropertySpec.builder("path", String::class)
.addModifiers(KModifier.CONST)
.initializer(
"%S + %S + %S + %S + %S + %S + %S",
parentPath,
RouteDivider,
name,
if (pathParams.any()) RouteDivider else "",
pathParams.joinToString(RouteDivider) { "{${it.name}}" },
if (queryParams.any()) "?" else "",
queryParams.joinToString("&") { "${it.name}={${it.name}}" },
)
.build(),
)
addFunction(
funSpec(
name,
it.parameters,
),
)
} else {
it.generateRoute().addTo(this)
}
}
}.build()
}
}
internal data class NestedRouteDefinition(
override var name: String,
override var parent: RouteDefinition? = null,
val childRoute: ArrayList = arrayListOf(),
) : RouteDefinition {
override fun generateRoute(): Taggable {
return TypeSpec.objectBuilder(name)
.addModifiers(KModifier.ACTUAL)
.apply {
childRoute.forEach {
it.generateRoute().addTo(this)
}
}
.build()
}
}
private fun Taggable.addTo(builder: TypeSpec.Builder) {
when (this) {
is TypeSpec -> builder.addType(this)
is FunSpec -> builder.addFunction(this)
is PropertySpec -> builder.addProperty(this)
}
}
internal data class ConstRouteDefinition(
override val name: String,
override val parent: RouteDefinition? = null,
private val isConst: Boolean,
) : RouteDefinition {
override fun generateRoute(): Taggable {
return PropertySpec.builder(name, String::class)
.addModifiers(KModifier.ACTUAL)
.apply {
if (isConst) {
addModifiers(KModifier.CONST)
}
}
.initializer("%S + %S + %S", parentPath, RouteDivider, name)
.build()
}
}
internal data class FunctionRouteDefinition(
override val name: String,
override val parent: RouteDefinition? = null,
val parameters: List,
) : RouteDefinition {
override fun generateRoute(): Taggable {
val p = parameters.filter { !it.parameter.type.resolve().isMarkedNullable }
return TypeSpec.objectBuilder(name)
.addModifiers(KModifier.ACTUAL)
.addFunction(
funSpec(name = name, parameters = parameters),
)
.addProperty(
PropertySpec.builder("path", String::class)
.addModifiers(KModifier.CONST)
.initializer(
"%S + %S + %S + %S + %S",
parentPath,
RouteDivider,
name,
RouteDivider,
p.joinToString(RouteDivider) { "{${it.name}}" },
)
.build(),
)
.build()
}
}
private fun RouteDefinition.funSpec(
name: String,
parameters: List,
): FunSpec {
val parameter = parameters.filter { !it.parameter.type.resolve().isMarkedNullable }
val query = parameters.filter { it.parameter.type.resolve().isMarkedNullable }
return FunSpec.builder("invoke")
.addModifiers(KModifier.OPERATOR, KModifier.ACTUAL)
.returns(String::class)
.addParameters(
parameters.map {
ParameterSpec.builder(it.name, it.type)
.build()
},
)
.addStatement("val path = %S + %S + %S", parentPath, RouteDivider, name)
.also {
if (parameter.any()) {
it.addStatement(
"val params = %S + %P",
RouteDivider,
parameter.joinToString(RouteDivider) { if (it.type.isString) "\${${encode(it.name)}}" else "\${${it.name}}" },
)
} else {
it.addStatement("val params = \"\"")
}
if (query.any()) {
it.addStatement(
"val query = \"?\" + %P",
query.joinToString("&") {
if (it.type.isStringNullable) {
"${it.name}=\${${encodeNullable(it.name)}}"
} else {
"${it.name}=\${if (${it.name} == null) \"\" else ${it.name}}"
}
},
)
} else {
it.addStatement("val query = \"\"")
}
}
.addStatement("return path + params + query")
.build()
}
private fun encode(value: String) = "java.net.URLEncoder.encode($value, \"UTF-8\")"
private fun encodeNullable(value: String) =
"java.net.URLEncoder.encode(if($value == null) \"\" else $value, \"UTF-8\")"
internal data class RouteParameter(
val name: String,
val type: TypeName,
val parameter: KSValueParameter,
)
internal val TypeName.isString get() = this == String::class.asTypeName()
internal val TypeName.isStringNullable get() = this == String::class.asTypeName().copy(nullable = true)
internal val TypeName.isBoolean get() = this == Boolean::class.asTypeName()
internal val TypeName.isLong get() = this == Long::class.asTypeName()
© 2015 - 2025 Weber Informatics LLC | Privacy Policy