KsError

@Target(allowedTargets = [AnnotationTarget.FUNCTION])
annotation class KsError(val code: Int, val type: KClass<*>)

Binds a @Serializable Throwable subclass to a wire-level integer error code on a KsMethod-annotated function.

Declared at the call site with one entry per bindable error type:

@KsMethod("/initialize")
@KsError(code = 100, type = InitError::class)
@KsError(code = 101, type = VersionError::class)
suspend fun initialize(params: InitParams): InitResult

The handler throws an instance of the bound type directly (no wrapper required); ksrpc encodes the throwable using its KSerializer and emits the bound code on the wire. The client deserializes back into the bound type and re-throws — callers catch (e: MyError) typed.

The code on the binding is the single source of truth for the wire code; there is no per-throw override mechanism. If you want a different code for the same data, bind it differently.

The bound type must:

  • Be @Serializable (validated by the ksrpc compiler plugin)

  • Extend Throwable (or any subclass — typically RuntimeException)

  • Declare only fields safe to serialize (no cause, no inherited stackTrace); message is typically computed from the serialized fields:

@Serializable
class InitError(val retry: Boolean, val reason: String) : RuntimeException() {
override val message: String get() = "init failed: $reason"
}

The server stack trace is NOT propagated across the wire — clients see a stack from local deserialization. Use message and the serialized fields for diagnostic info.

The plugin captures KSerializer<type> and exposes a bidirectional map on the generated RpcMethod descriptor — forward (code → KSerializer) for client-side deserialization, reverse (KClass → code + KSerializer) for server-side resolution of a thrown t::class. Unlike sibling annotations propagated via @KsMethodMetadata, this marker is consumed by the compiler plugin directly because the binding requires a real serializer reference and a dedicated lookup table, not an opaque metadata bag.

Samples

import com.monkopedia.ksrpc.RpcService
import com.monkopedia.ksrpc.annotation.KsError
import com.monkopedia.ksrpc.annotation.KsMethod
import com.monkopedia.ksrpc.annotation.KsService
import com.monkopedia.ksrpc.ksrpcEnvironment
import com.monkopedia.ksrpc.rpcObject
import com.monkopedia.ksrpc.serialized
import com.monkopedia.ksrpc.toStub
import kotlinx.serialization.Serializable

fun main() { 
   //sampleStart 
   // @KsError binds a @Serializable Throwable subclass to an integer error code.
// Multiple @KsError annotations can appear on the same method.
// The compiler plugin captures the bidirectional code <-> serializer map.
val rpcObj = rpcObject<DocumentService>()
val createEndpoint = rpcObj.findEndpoint("create")
val getEndpoint = rpcObj.findEndpoint("get") 
   //sampleEnd
}
import com.monkopedia.ksrpc.RpcService
import com.monkopedia.ksrpc.annotation.KsError
import com.monkopedia.ksrpc.annotation.KsMethod
import com.monkopedia.ksrpc.annotation.KsService
import com.monkopedia.ksrpc.ksrpcEnvironment
import com.monkopedia.ksrpc.rpcObject
import com.monkopedia.ksrpc.serialized
import com.monkopedia.ksrpc.toStub
import kotlinx.serialization.Serializable

fun main() { 
   //sampleStart 
   // Server implementation throws typed errors directly.
val service = object : DocumentService {
    override suspend fun createDocument(content: String): String {
        if (content.isBlank()) {
            throw ValidationError("content", "must not be blank")
        }
        return "doc-001"
    }

    override suspend fun getDocument(id: String): String {
        if (!id.startsWith("doc-")) {
            throw ValidationError("id", "invalid format")
        }
        throw NotFoundError(id)
    }
}

val env = ksrpcEnvironment { }
val serialized = service.serialized(env)
val stub = serialized.toStub<DocumentService, String>()

// Clients catch the original typed exception after deserialization.
try {
    stub.getDocument("doc-missing")
} catch (e: NotFoundError) {
    // e.resourceId == "doc-missing"
    println("Not found: ${e.resourceId}")
} catch (e: ValidationError) {
    println("Validation: ${e.field} - ${e.reason}")
} 
   //sampleEnd
}

Properties

Link copied to clipboard
val code: Int
Link copied to clipboard
val type: KClass<*>