code
package site.daydream.colleen
import java.io.InputStream
import kotlin.reflect.KFunction
import kotlin.reflect.KParameter
import kotlin.reflect.KType
import kotlin.reflect.full.findAnnotation
// ===== Exception Definitions =====
sealed class ExtractionError(message: String, cause: Throwable? = null) : RuntimeException(message, cause) {
class MissingParameter(name: String, type: String) :
ExtractionError("Required parameter '$name' of type $type not found")
class InvalidType(type: String, reason: String) :
ExtractionError("Invalid type $type: $reason")
class ConversionFailed(value: String, from: String, to: String, cause: Throwable? = null) :
ExtractionError("Cannot convert '$value' from $from to $to", cause)
}
// ===== Annotations =====
@Target(AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
annotation class Param(val value: String = "")
// ===== 核心设计:两阶段提取 =====
/**
* 参数提取器基类
* 只负责携带值,不负责提取逻辑
*/
abstract class ParamExtractor<T>(val value: T)
/**
* 提取器工厂接口
* 传递完整的参数元信息,提取器根据需要使用
*/
interface ExtractorFactory<T : ParamExtractor<*>> {
/**
* 构建提取函数
* @param param 参数的完整元信息(包含名称、类型、注解等)
*/
fun build(param: KParameter): (Context) -> T
}
// ===== Parameter Wrapper Types =====
class Path<T>(value: T) : ParamExtractor<T>(value) {
companion object : ExtractorFactory<Path<*>> {
override fun build(param: KParameter): (Context) -> Path<*> {
val name = param.paramName()
val type = param.innerType()
require(name.isNotEmpty()) { "Path parameter requires @Param annotation" }
require(!type.isMarkedNullable) { "Path parameter cannot be nullable" }
val targetClass = type.toClass()
return { ctx ->
val value = ctx.param(name)
?: throw ExtractionError.MissingParameter(name, "Path")
Path(value.convertTo(targetClass, name))
}
}
}
}
class Header(value: String) : ParamExtractor<String>(value) {
companion object : ExtractorFactory<Header> {
override fun build(param: KParameter): (Context) -> Header {
val name = param.paramName()
require(name.isNotEmpty()) { "Header parameter requires @Param annotation" }
return { ctx -> Header(ctx.header(name) ?: "") }
}
}
}
class Cookie(value: String) : ParamExtractor<String>(value) {
companion object : ExtractorFactory<Cookie> {
override fun build(param: KParameter): (Context) -> Cookie {
val name = param.paramName()
require(name.isNotEmpty()) { "Cookie parameter requires @Param annotation" }
return { ctx -> Cookie(ctx.cookie(name) ?: "") }
}
}
}
class Query<T>(value: T) : ParamExtractor<T>(value) {
companion object : ExtractorFactory<Query<*>> {
override fun build(param: KParameter): (Context) -> Query<*> {
val name = param.paramName()
val type = param.innerType()
val targetClass = type.toClass()
// Query<Map<String, List<String>>>
if (targetClass.isMap()) {
return { ctx -> Query(ctx.queries()) }
}
// Query<List<T>>
if (targetClass.isList()) {
val elementClass = type.listElementClass()
return { ctx ->
val values = ctx.request.queryAll(name)
Query(values.map { it.convertTo(elementClass, name) })
}
}
// Query<Int> 或 Query<Int?>
if (targetClass.isSimple()) {
return { ctx ->
val value = ctx.query(name)
when {
value != null -> Query(value.convertTo(targetClass, name))
type.isMarkedNullable -> Query(null)
else -> throw ExtractionError.MissingParameter(name, "Query")
}
}
}
// Query<CustomDto>
return { ctx ->
try {
Query(ctx.queriesAs(targetClass)!!)
} catch (e: Exception) {
throw ExtractionError.ConversionFailed("query data", "Query", targetClass.simpleName, e)
}
}
}
}
}
class Text(value: String) : ParamExtractor<String>(value) {
companion object : ExtractorFactory<Text> {
override fun build(param: KParameter): (Context) -> Text {
// Text 不需要任何参数信息
return { ctx -> Text(ctx.text() ?: "") }
}
}
}
class Json<T>(value: T) : ParamExtractor<T>(value) {
companion object : ExtractorFactory<Json<*>> {
override fun build(param: KParameter): (Context) -> Json<*> {
val targetClass = param.innerType().toClass()
return { ctx ->
try {
Json(ctx.jsonAs(targetClass))
} catch (e: Exception) {
throw ExtractionError.ConversionFailed("request body", "JSON", targetClass.simpleName, e)
}
}
}
}
}
class Form<T>(value: T) : ParamExtractor<T>(value) {
companion object : ExtractorFactory<Form<*>> {
override fun build(param: KParameter): (Context) -> Form<*> {
val name = param.paramName()
val type = param.innerType()
val targetClass = type.toClass()
if (targetClass.isMap()) {
return { ctx -> Form(ctx.forms()) }
}
if (targetClass.isList()) {
val elementClass = type.listElementClass()
return { ctx ->
val values = ctx.request.formAll(name)
Form(values.map { it.convertTo(elementClass, name) })
}
}
if (targetClass.isSimple()) {
return { ctx ->
val value = ctx.form(name)
when {
value != null -> Form(value.convertTo(targetClass, name))
type.isMarkedNullable -> Form(null)
else -> throw ExtractionError.MissingParameter(name, "Form")
}
}
}
return { ctx ->
try {
Form(ctx.formsAs(targetClass)!!)
} catch (e: Exception) {
throw ExtractionError.ConversionFailed("form data", "Form", targetClass.simpleName, e)
}
}
}
}
}
class Stream(value: InputStream) : ParamExtractor<InputStream>(value) {
companion object : ExtractorFactory<Stream> {
override fun build(param: KParameter): (Context) -> Stream {
// Stream 不需要任何参数信息
return { ctx ->
Stream(ctx.request.stream ?: throw ExtractionError.MissingParameter("stream", "InputStream"))
}
}
}
}
class UploadedFile(value: Request.UploadedFile) : ParamExtractor<Request.UploadedFile>(value) {
companion object : ExtractorFactory<UploadedFile> {
override fun build(param: KParameter): (Context) -> UploadedFile {
val name = param.paramName().ifEmpty { "file" }
return { ctx ->
val file = ctx.file(name)
?: throw ExtractionError.MissingParameter(name, "UploadedFile")
UploadedFile(file)
}
}
}
}
// ===== cx Function Implementation =====
fun cx(fn: KFunction<*>, instance: Any? = null): Handler {
val instanceParam = fn.parameters.firstOrNull { it.kind == KParameter.Kind.INSTANCE }
val valueParams = fn.parameters.filter { it.kind == KParameter.Kind.VALUE }
// 构建阶段:预先生成所有提取函数
val extractors = valueParams.map { param -> buildExtractor(param) }
return Handler { ctx ->
val args = buildMap {
if (instanceParam != null && instance != null) {
put(instanceParam, instance)
}
// 执行阶段:只需调用提取函数
valueParams.forEachIndexed { index, param ->
put(param, extractors[index](ctx))
}
}
fn.callBy(args)
}
}
// ===== Core Extractor Builder =====
private fun buildExtractor(param: KParameter): (Context) -> Any? {
val kType = param.type
val wrapperClass = kType.classifier as? kotlin.reflect.KClass<*>
?: throw ExtractionError.InvalidType(kType.toString(), "Cannot resolve classifier")
// Context 直接传递
if (wrapperClass == Context::class) {
return { it }
}
// 检查是否是 ParamExtractor
if (ParamExtractor::class.java.isAssignableFrom(wrapperClass.java)) {
val factory = getExtractorFactory(wrapperClass)
// 直接传递 KParameter,让提取器自己决定需要什么信息
return factory.build(param)
}
// 回退到服务注入
return { ctx -> ctx.getService(wrapperClass.java) }
}
private fun getExtractorFactory(wrapperClass: kotlin.reflect.KClass<*>): ExtractorFactory<*> {
return try {
// Kotlin: companion object
wrapperClass.companionObjectInstance as? ExtractorFactory<*>
} catch (e: Exception) {
null
} ?: try {
// Java: FACTORY 静态字段
wrapperClass.java.getField("FACTORY").get(null) as? ExtractorFactory<*>
} catch (e: Exception) {
null
} ?: throw ExtractionError.InvalidType(
wrapperClass.simpleName ?: "Unknown",
"ParamExtractor must have a companion object (Kotlin) or FACTORY field (Java) implementing ExtractorFactory"
)
}
// ===== Helper Methods =====
// KParameter 扩展方法
private fun KParameter.paramName(): String {
return this.findAnnotation<Param>()?.value?.takeIf { it.isNotEmpty() }
?: this.name
?: ""
}
private fun KParameter.innerType(): KType {
return this.type.arguments.firstOrNull()?.type
?: throw ExtractionError.InvalidType(this.type.toString(), "Missing generic type parameter")
}
// KType 扩展方法
private fun KType.toClass(): Class<*> {
return (classifier as? kotlin.reflect.KClass<*>)?.java
?: throw ExtractionError.InvalidType(toString(), "Cannot resolve class")
}
private fun KType.listElementClass(): Class<*> {
val elementType = arguments.firstOrNull()?.type
?: throw ExtractionError.InvalidType(toString(), "List missing element type")
return elementType.toClass()
}
// String 类型转换
internal fun String.convertTo(targetClass: Class<*>, paramName: String): Any {
return try {
when (targetClass) {
String::class.java -> this
Int::class.java, Integer::class.java ->
toIntOrNull() ?: throw ExtractionError.ConversionFailed(this, "String", "Int")
Long::class.java, java.lang.Long::class.java ->
toLongOrNull() ?: throw ExtractionError.ConversionFailed(this, "String", "Long")
Double::class.java, java.lang.Double::class.java ->
toDoubleOrNull() ?: throw ExtractionError.ConversionFailed(this, "String", "Double")
Float::class.java, java.lang.Float::class.java ->
toFloatOrNull() ?: throw ExtractionError.ConversionFailed(this, "String", "Float")
Boolean::class.java, java.lang.Boolean::class.java ->
toLenientBoolean()
else ->
throw ExtractionError.InvalidType(targetClass.simpleName, "Unsupported conversion type")
}
} catch (e: ExtractionError) {
throw e
} catch (e: Exception) {
throw ExtractionError.ConversionFailed(this, "String", targetClass.simpleName, e)
}
}
internal fun String.toLenientBoolean(): Boolean {
return lowercase() in setOf("true", "1", "yes", "y")
}
internal fun Class<*>.isSimple() = this in SIMPLE_TYPES
internal fun Class<*>.isMap() = Map::class.java.isAssignableFrom(this)
internal fun Class<*>.isList() = List::class.java.isAssignableFrom(this)
internal val SIMPLE_TYPES = setOf(
String::class.java,
Int::class.java, Integer::class.java,
Long::class.java, java.lang.Long::class.java,
Double::class.java, java.lang.Double::class.java,
Float::class.java, java.lang.Float::class.java,
Boolean::class.java, java.lang.Boolean::class.java
)