All Downloads are FREE. Search and download functionalities are using the official Maven repository.

main.io.github.seiko.precompose.RouteDefinition.kt Maven / Gradle / Ivy

There is a newer version: 1.1.0
Show newest version
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