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

commonMain.com.caesarealabs.rpc4k.runtime.api.RpcServerUtils.kt Maven / Gradle / Ivy

package com.caesarealabs.rpc4k.runtime.api

import com.caesarealabs.logging.Logging
import com.caesarealabs.rpc4k.runtime.implementation.RpcResult
import com.caesarealabs.rpc4k.runtime.user.RPCContext

public interface ServerData {

}

public object RpcServerUtils {
    /**
     * Should be called by server implementations whenever a new request is received, and pass the request's bytes as [input].
     * A response must then be returned to the client according to the [RpcResult].
     * This will call the RPC Service, using code gen and such.
     *
     * @param serverData Information specific to the server implementation calling this. RPC services may then
     * reference this data through [RPCContext.serverData] (usually needing an `is` check specific to the server implementation)
     * @param initialLogs Logs to perform on the call, independent of the endpoint.
     */
    public suspend fun routeCall(
        input: ByteArray,
        config: ServerConfig,
        serverData: ServerData? = null,
        initialLogs: Logging.() -> Unit = {}
    ): RpcResult {
        // Logging not available yet - make do with normal prints
        val method = try {
            Rpc.peekMethodName(input)
        } catch (e: InvalidRpcRequestException) {
            return config.config.logging.wrapCall("Other Request Failures") {
                initialLogs()
                invalidRequest(e, this@wrapCall)
            }
        } catch (e: Throwable) {
            return config.config.logging.wrapCall("Other Internal Req Failures") {
                initialLogs()
                serverError(e, this@wrapCall) { "Failed to call Rpc.peekMethodName()" }
            }
        }
        // Logging available
        return config.config.logging.wrapCall(method) {
            initialLogs()
            val logging = this@wrapCall
            try {
                (config.router as RpcRouter).routeRequest(input, method, config.config, SimpleRpcContext(serverData, logging))
                    ?.let { RpcResult.Success(it) }
                    ?: RpcResult.Error("Non existent procedure $method", RpcError.InvalidRequest)
            } catch (e: InvalidRpcRequestException) {
                invalidRequest(e, logging)
            } catch (e: Throwable) {
                serverError(e, logging) { "Failed to call routeRequest() on method $method" }
            }
        }
    }

    private fun invalidRequest(exception: InvalidRpcRequestException, logging: Logging): RpcResult {
        logging.logWarn(exception) { "Invalid request. Is the content-type header correct?" }
        // RpcServerException messages are trustworthy
        return RpcResult.Error(exception.message, RpcError.InvalidRequest)
    }

    private fun serverError(exception: Throwable, logging: Logging, logMessage: () -> String): RpcResult {
        logging.logError(exception, logMessage)
        // Don't send arbitrary throwable messages because it could leak data
        return RpcResult.Error("Server failed to process request", RpcError.InternalError)
    }
}

public class SimpleRpcContext(override val serverData: ServerData?, private val logging: Logging) : RPCContext, Logging by logging









© 2015 - 2024 Weber Informatics LLC | Privacy Policy