Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
package com.caesarealabs.rpc4k.runtime.implementation
import com.benasher44.uuid.uuid4
import com.caesarealabs.logging.Logging
import com.caesarealabs.rpc4k.runtime.api.*
import com.caesarealabs.rpc4k.runtime.implementation.serializers.TupleSerializer
import com.caesarealabs.rpc4k.runtime.user.EventSubscription
import com.caesarealabs.rpc4k.runtime.user.RPCContext
import kotlinx.coroutines.flow.map
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerializationException
/**
* These functions are used by generated code and code that interacts with them
*/
//TO DO: consider making the C2S functions be in a instance method, this would allow storing the format and client and reduce codegen.
public object GeneratedCodeUtils {
/**
* Sends a value and returns the result
*/
public suspend fun request(
client: RpcClient,
format: SerializationFormat,
methodName: String,
args: List,
argSerializers: List>,
responseSerializer: KSerializer<*>
): T {
val rpc = Rpc(methodName, args)
val result = client.send(rpc, format, argSerializers)
return format.decode(responseSerializer, result) as T
}
/**
* Sends a value, not caring about the result
*/
public suspend fun send(
client: RpcClient, format: SerializationFormat, methodName: String, args: List,
argSerializers: List>
) {
val rpc = Rpc(methodName, args)
client.send(rpc, format, argSerializers)
}
/**
* Creates a new [EventSubscription] that is a _cold_ flow that allows listening to an event.
* @param target Note that here we don't have an issue with 'empty string' conflicting with 'no target' because
* the server already defines when a target is necessary. When a target is needed, empty string is interpreted as empty string,
* when a target is not needed, empty string, null, or anything else will be treated as 'no target'.
*/
public fun coldEventFlow(
client: RpcClient,
format: SerializationFormat,
event: String,
args: List<*>,
argSerializers: List>,
eventSerializer: KSerializer,
target: Any? = null
): EventSubscription {
val listenerId = uuid4().toString()
val payload = format.encode(TupleSerializer(argSerializers), args)
val subscriptionMessage = C2SEventMessage.Subscribe(event = event, listenerId = listenerId, payload, target?.toString())
val unsubMessage = C2SEventMessage.Unsubscribe(event = event, listenerId = listenerId)
val flow = client.events.createFlow(subscriptionMessage.toByteArray(), unsubMessage.toByteArray(), listenerId)
.map { format.decode(eventSerializer, it) }
return EventSubscription(listenerId, flow)
}
/**
* Called by the generated Router, to respond to the client after receiving a request.
* [argDeserializers], [resultSerializer], and [respondMethod] are unique for each procedure
* and so need special codegen to generate them.
*/
public suspend fun respond(
config: HandlerConfig<*>,
request: ByteArray,
argDeserializers: List>,
resultSerializer: KSerializer,
context: RPCContext,
respondMethod: suspend RPCContext.(args: List<*>) -> T
): ByteArray {
val parsed = try {
Rpc.fromByteArray(request, config.format, argDeserializers)
} catch (e: SerializationException) {
throw InvalidRpcRequestException("Malformed request arguments: ${e.message}", e)
}
context.logData("Parameters") { parsed.arguments }
val result = with(context) { respondMethod(parsed.arguments) }
context.logData("Response") { result }
val response = config.format.encode(resultSerializer, result)
return response
}
public suspend fun invokeEvent(
config: HandlerConfig,
eventName: String,
subArgDeserializers: List>,
resultSerializer: KSerializer,
context: RPCContext,
/**
* The actors that actually produced this event, and will not want to get updated that this event occurred, because they
* already updated the outcome of said event in memory.
*/
participants: Set,
/**
* Important - pass null when targets are not used in the event,
* pass .toString() when targets are used in the event. The null value should be equivalent to the "null" value, when targets are relevant.
*/
target: String? = null,
handle: suspend (subArgs: List<*>) -> R
) {
val match = config.eventManager.match(eventName, target)
// config.logging.wrapCall(eventName) {
context.logInfo { "Invoking event $eventName" }
for ((i, subscriber) in match.withIndex()) {
// Don't send events to participants
if (subscriber.info.listenerId in participants) continue
val parsed = config.format.decode(TupleSerializer(subArgDeserializers), subscriber.info.data)
// logData("Listener ID $i") { subscriber.info.listenerId }
// logData("Subscription Data $i") { parsed }
// logInfo { "Processing subscription ${subscriber.info.listenerId}" }
val handled = handle(parsed)
// logData("Event $i") { handled }
context.logVerbose {
"Dispatching event $eventName to listener num $i, with ID ${subscriber.info.listenerId}" +
", subscription data $parsed, and resulting event $handled"
}
val bytes = config.format.encode(resultSerializer, handled)
val fullMessage = S2CEventMessage.Emitted(subscriber.info.listenerId, bytes).toByteArray()
config.sendOrDrop(subscriber.connection, fullMessage, context)
}
// }
}
}
/**
* Will send the [bytes] to the [connection], dropping it if it cannot be reached
*/
internal suspend fun HandlerConfig.sendOrDrop(connection: EventConnection, bytes: ByteArray, logging: Logging) {
val clientExists = messageLauncher.send(connection, bytes)
if (!clientExists) {
logging.logInfo { "Dropping connection ${connection.id} as it cannot be reached" }
eventManager.dropClient(connection)
}
}