Skip to content

y9vad9/rsocket-kotlin-router

Repository files navigation

GitHub release GitHub

RSocket Router

rsocket-kotlin-router is a customisable library designed to streamline and simplify routing for RSocket Kotlin server applications. This library offers a typesafe DSL for handling various routes, serving as a declarative simplified alternative to manual routing that would otherwise result in long-winded ternary logic or exhaustive when statements.

Library provides the following features:

How to use

First of all, you need to implement basic artifacts with routing support. For now, rsocket-kotlin-router is available only at my self-hosted maven:

repositories {
    maven("https://maven.y9vad9.com")
}

dependencies {
    implementation("com.y9vad9.rsocket.router:router-core:$version")
}

For now, it's available for JVM only, but as there is no JVM platform API used, new targets will be available upon your request.

Example of defining RSocket router:

val serverRouter = router {
    routeSeparator = '.'
    routeProvider { metadata: ByteReadPacket? ->
        metadata?.read(RoutingMetadata)?.tags?.first()
            ?: throw RSocketError.Invalid("No routing metadata was provided")
    }

    routing { // this: RoutingBuilder
        route("authorization") {
            requestResponse("register") { payload: Payload ->
                // just 4 example
                println(payload.data.readText())
                Payload.Empty
            }
        }
    }
}

See also what else is supported:

Interceptors Interceptors are experimental feature: API can be changed in the future.

Preprocessors

Preprocessors are utilities that run before routing feature applies. For cases, when you need to transform input into something or propagate values using coroutines – you can extend Preprocessor.Modifier or Preprocessor.CoroutineContext. Here's an example:

class MyCoroutineContextElement(val value: String) : CoroutineContext.Element {... }

@OptIn(ExperimentalInterceptorsApi::class)
class MyCoroutineContextPreprocessor : Preprocessor.CoroutineContext {
    override fun intercept(coroutineContext: CoroutineContext, input: Payload): CoroutineContext {
        return coroutineContext + MyCoroutineContextElement(value = "smth")
    }
}

Route Interceptors

In addition to the Preprocessors, rsocket-kotlin-router also provides API to intercept specific routes:

@OptIn(ExperimentalInterceptorsApi::class)
class MyRouteInterceptor : RouteInterceptor.Modifier {
    override fun intercept(route: String, input: Payload): Payload {
        return Payload.Empty // just for example
    }
}

Installation

val serverRouter = router {
    preprocessors {
        forCoroutineContext(MyCoroutineContextPreprocessor())
    }

    sharedInterceptors {
        forModification(MyRouteInterceptor())
    }
}
Versioning support

Here's example of how request versioning looks like:

requestResponseV("foo") {
    version(1) { payload: Payload ->
        // handle requests for version "1.0"
        Payload.Empty
    }
    version(2) { payload: Payload ->
        // handle requests for version "2.0"
        Payload.Empty
    }
}

For details, please refer to the versioning guide.

Serialization support

Here is example of how type-safe requests with serialization/deserialization mechanisms look like:

requestResponse<Foo, Bar>("register") { foo: Foo ->
    return@requestResponse Bar(/* ... */)
}

// or versioned variant:

requestResponseV("register") {
    version(1) { foo: Foo ->
        Bar(/* ... */)
    }

    version(2) { qux: Qux ->
        FizzBuzz(/* ... */)
    }
}

For details, please refer to the serialization guide.

Testing

rsocket-kotlin-router provides ability to test your routes with router-test artifact:

dependencies {
    implementation("com.y9vad9.rsocket.router:router-test:$version")
}
@Test
fun testRoutes() {
    runBlocking {
        val route1 = router.routeAtOrAssert("test")
        val route2 = router.routeAtOrAssert("test.subroute")

        route1.assertHasInterceptor<MyInterceptor>()
        route2.assertHasInterceptor<MyInterceptor>()

        route2.fireAndForgetOrAssert(buildPayload {
            data("test")
        })
    }
}

You can refer to the example for more details.

Bugs and Feedback

For bugs, questions and discussions please use the GitHub Issues.

License

This library is licensed under MIT License. Feel free to use, modify, and distribute it for any purpose.