refactor(#1375): type connectors repository RPC params (#1387)

This commit is contained in:
m57 authored and GitHub committed 2026-09-29 20:27:27 +03:00
1 parent 60c13f10ce
commit 8ae2ddbbee
10 files changed
+1286 -149

No files matched your search

@@ -7,6 +7,19 @@ import com.m57.hermescontrol.data.model.ConnectorCatalogEntry
import com.m57.hermescontrol.data.model.ConnectorError
import com.m57.hermescontrol.data.model.ConnectorPolicy
import com.m57.hermescontrol.data.model.ConnectorTool
import com.m57.hermescontrol.data.ws.contract.ConnectorOwner
import com.m57.hermescontrol.data.ws.contract.ConnectorsAccountsParams
import com.m57.hermescontrol.data.ws.contract.ConnectorsAccountsRemoveParams
import com.m57.hermescontrol.data.ws.contract.ConnectorsCatalogParams
import com.m57.hermescontrol.data.ws.contract.ConnectorsConnectParams
import com.m57.hermescontrol.data.ws.contract.ConnectorsListParams
import com.m57.hermescontrol.data.ws.contract.ConnectorsOperationStatusParams
import com.m57.hermescontrol.data.ws.contract.ConnectorsPolicyGetParams
import com.m57.hermescontrol.data.ws.contract.ConnectorsPolicySetParams
import com.m57.hermescontrol.data.ws.contract.ConnectorsToolsParams
import com.m57.hermescontrol.data.ws.contract.HermesRpcCaller
import com.m57.hermescontrol.data.ws.contract.RpcMethods
import com.m57.hermescontrol.data.ws.contract.TypedRpcCaller
import kotlinx.coroutines.CancellationException
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonObject
@@ -74,6 +87,7 @@ class HermesAccountConnectorRepository(
if (!deferred.isCompleted) deferred.cancel()
}
},
private val caller: TypedRpcCaller = HermesRpcCaller,
) : AccountConnectorRepository {
private suspend fun request(
method: String,
@@ -116,10 +130,11 @@ class HermesAccountConnectorRepository(
override suspend fun listConnectors(): com.m57.hermescontrol.data.model.ConnectorListResult =
try {
ConnectorParser.parseListResult(
rpc(
WsMethods.CONNECTORS_LIST,
accountParams(
mapOf("owner" to mapOf("type" to "account")),
caller.call(
RpcMethods.CONNECTORS_LIST,
ConnectorsListParams(
owner = ConnectorOwner.account(),
profile = AuthManager.activeProfileId.value,
),
),
)
@@ -146,14 +161,13 @@ class HermesAccountConnectorRepository(
) = guarded {
require(slugs.isNotEmpty() && slugs.all(ConnectorRepository::isValidSlug))
parseOperation(
request(
WsMethods.CONNECTORS_CONNECT,
accountParams(
mapOf(
"owner" to mapOf("type" to "account"),
"connectors" to slugs,
"reconnect" to reconnect,
),
caller.call(
RpcMethods.CONNECTORS_CONNECT,
ConnectorsConnectParams(
owner = ConnectorOwner.account(),
connectors = slugs,
reconnect = reconnect,
profile = AuthManager.activeProfileId.value,
),
),
)
@@ -166,7 +180,16 @@ class HermesAccountConnectorRepository(
override suspend fun operationStatus(opId: String) =
guarded {
parseOperation(operationRequest(WsMethods.CONNECTORS_OPERATION_STATUS, mapOf("op_id" to opId)))
parseOperation(
caller.call(
RpcMethods.CONNECTORS_OPERATION_STATUS,
ConnectorsOperationStatusParams(
owner = ConnectorOwner.account(),
opId = opId,
profile = AuthManager.activeProfileId.value,
),
),
)
}
@Suppress("UNCHECKED_CAST")
@@ -180,7 +203,14 @@ class HermesAccountConnectorRepository(
override suspend fun catalog() =
guarded {
obj(request(WsMethods.CONNECTORS_CATALOG, accountParams()))["connectors"]!!.jsonArray.map {
val response =
caller.call(
RpcMethods.CONNECTORS_CATALOG,
ConnectorsCatalogParams(
profile = AuthManager.activeProfileId.value,
),
)
obj(response)["connectors"]!!.jsonArray.map {
val row = it.jsonObject
ConnectorCatalogEntry(row.str("slug"), row.str("name"), row.str("description"), row.str("category"))
}
@@ -188,7 +218,14 @@ class HermesAccountConnectorRepository(
override suspend fun accounts() =
guarded {
obj(request(WsMethods.CONNECTORS_ACCOUNTS, accountParams()))["accounts"]!!.jsonArray.map { e ->
val response =
caller.call(
RpcMethods.CONNECTORS_ACCOUNTS,
ConnectorsAccountsParams(
profile = AuthManager.activeProfileId.value,
),
)
obj(response)["accounts"]!!.jsonArray.map { e ->
val r = e.jsonObject
ConnectorAccount(
r.str("connection_id"),
@@ -209,10 +246,11 @@ class HermesAccountConnectorRepository(
require(connectionId.isNotBlank())
val response =
obj(
request(
WsMethods.CONNECTORS_ACCOUNTS_REMOVE,
accountParams(
mapOf("connection_id" to connectionId),
caller.call(
RpcMethods.CONNECTORS_ACCOUNTS_REMOVE,
ConnectorsAccountsRemoveParams(
connectionId = connectionId,
profile = AuthManager.activeProfileId.value,
),
),
)
@@ -225,12 +263,16 @@ class HermesAccountConnectorRepository(
refresh: Boolean,
) = guarded {
require(ConnectorRepository.isValidSlug(slug))
val params =
buildMap<String, Any> {
put("slug", slug)
if (refresh) put("refresh", true)
}
obj(request(WsMethods.CONNECTORS_TOOLS, accountParams(params)))["tools"]!!.jsonArray.map { e ->
val response =
caller.call(
RpcMethods.CONNECTORS_TOOLS,
ConnectorsToolsParams(
slug = slug,
refresh = if (refresh) true else null,
profile = AuthManager.activeProfileId.value,
),
)
obj(response)["tools"]!!.jsonArray.map { e ->
val r = e.jsonObject
ConnectorTool(
r.str("slug"),
@@ -245,7 +287,15 @@ class HermesAccountConnectorRepository(
override suspend fun policy() =
guarded {
val root = obj(request(WsMethods.CONNECTORS_POLICY_GET, accountParams()))
val root =
obj(
caller.call(
RpcMethods.CONNECTORS_POLICY_GET,
ConnectorsPolicyGetParams(
profile = AuthManager.activeProfileId.value,
),
),
)
val layers = root.array("layers").map { it.jsonObject }
val member = layers.firstOrNull { it.str("kind") == "member" }
parsePolicy(root["effective"]!!.jsonObject).copy(
@@ -293,13 +343,12 @@ class HermesAccountConnectorRepository(
check(scope == AuthManager.currentDataScope()) { "Account scope changed" }
val result =
obj(
request(
WsMethods.CONNECTORS_POLICY_SET,
accountParams(
mapOf(
"change" to change,
"expected_revision" to expectedRevision,
),
caller.call(
RpcMethods.CONNECTORS_POLICY_SET,
ConnectorsPolicySetParams(
change = ConnectorParser.toJsonElement(change) as JsonObject,
expectedRevision = expectedRevision,
profile = AuthManager.activeProfileId.value,
),
),
)
@@ -3,6 +3,12 @@ package com.m57.hermescontrol.data.ws
import com.m57.hermescontrol.data.model.ConnectorConnectResult
import com.m57.hermescontrol.data.model.ConnectorError
import com.m57.hermescontrol.data.model.ConnectorListResult
import com.m57.hermescontrol.data.ws.contract.ConnectorOwner
import com.m57.hermescontrol.data.ws.contract.ConnectorsConnectParams
import com.m57.hermescontrol.data.ws.contract.ConnectorsListParams
import com.m57.hermescontrol.data.ws.contract.HermesRpcCaller
import com.m57.hermescontrol.data.ws.contract.RpcMethods
import com.m57.hermescontrol.data.ws.contract.TypedRpcCaller
import kotlinx.coroutines.CancellationException
/**
@@ -42,19 +48,7 @@ interface ConnectorRepository {
* Default implementation of [ConnectorRepository] backed by [HermesWsClient].
*/
class HermesConnectorRepository(
private val rpcRequest: suspend (method: String, params: Map<String, Any>) -> Any? = { method, params ->
val deferred = HermesWsClient.request(method, params)
try {
deferred.await()
} catch (e: CancellationException) {
deferred.cancel(e)
throw e
} finally {
if (!deferred.isCompleted) {
deferred.cancel()
}
}
},
private val caller: TypedRpcCaller = HermesRpcCaller,
) : ConnectorRepository {
override suspend fun listConnectors(sessionId: String): ConnectorListResult {
if (sessionId.isBlank()) {
@@ -64,8 +58,8 @@ class HermesConnectorRepository(
}
return try {
val params = mapOf("owner" to ConnectorRepository.sessionOwner(sessionId))
val result = rpcRequest(WsMethods.CONNECTORS_LIST, params)
val params = ConnectorsListParams(owner = ConnectorOwner.session(sessionId))
val result = caller.call(RpcMethods.CONNECTORS_LIST, params)
ConnectorParser.parseListResult(result)
} catch (e: CancellationException) {
throw e
@@ -107,12 +101,12 @@ class HermesConnectorRepository(
return try {
val params =
mapOf(
"owner" to ConnectorRepository.sessionOwner(sessionId),
"connectors" to connectors,
"reconnect" to reconnect,
ConnectorsConnectParams(
owner = ConnectorOwner.session(sessionId),
connectors = connectors,
reconnect = reconnect,
)
val result = rpcRequest(WsMethods.CONNECTORS_CONNECT, params)
val result = caller.call(RpcMethods.CONNECTORS_CONNECT, params)
ConnectorParser.parseConnectResult(result)
} catch (e: CancellationException) {
throw e
@@ -0,0 +1,74 @@
package com.m57.hermescontrol.data.ws.contract
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonObject
@Serializable
data class ConnectorOwner(
@SerialName("type") val type: String,
@SerialName("session_id") val sessionId: String? = null,
) {
companion object {
fun account() = ConnectorOwner("account")
fun session(sessionId: String) = ConnectorOwner("session", sessionId)
}
}
@Serializable
data class ConnectorsListParams(
@SerialName("owner") val owner: ConnectorOwner,
@SerialName("profile") val profile: String? = null,
)
@Serializable
data class ConnectorsConnectParams(
@SerialName("owner") val owner: ConnectorOwner,
@SerialName("connectors") val connectors: List<String>,
@SerialName("reconnect") val reconnect: Boolean? = null,
@SerialName("profile") val profile: String? = null,
)
@Serializable
data class ConnectorsOperationStatusParams(
@SerialName("owner") val owner: ConnectorOwner,
@SerialName("op_id") val opId: String,
@SerialName("profile") val profile: String? = null,
)
@Serializable
data class ConnectorsCatalogParams(
@SerialName("profile") val profile: String? = null,
)
@Serializable
data class ConnectorsAccountsParams(
@SerialName("connector") val connector: String? = null,
@SerialName("profile") val profile: String? = null,
)
@Serializable
data class ConnectorsAccountsRemoveParams(
@SerialName("connection_id") val connectionId: String,
@SerialName("profile") val profile: String? = null,
)
@Serializable
data class ConnectorsPolicyGetParams(
@SerialName("profile") val profile: String? = null,
)
@Serializable
data class ConnectorsPolicySetParams(
@SerialName("change") val change: JsonObject,
@SerialName("expected_revision") val expectedRevision: String,
@SerialName("profile") val profile: String? = null,
)
@Serializable
data class ConnectorsToolsParams(
@SerialName("slug") val slug: String,
@SerialName("refresh") val refresh: Boolean? = null,
@SerialName("profile") val profile: String? = null,
)
@@ -1,5 +1,6 @@
package com.m57.hermescontrol.data.ws.contract
import com.m57.hermescontrol.data.ws.HermesWsClient
import com.m57.hermescontrol.data.ws.WsMethods
import kotlinx.serialization.KSerializer
import kotlinx.serialization.json.JsonElement
@@ -18,6 +19,20 @@ interface TypedRpcSender {
): String
}
interface TypedRpcCaller {
suspend fun <P, R> call(
method: RpcMethod<P, R>,
params: P,
): R
}
object HermesRpcCaller : TypedRpcCaller {
override suspend fun <P, R> call(
method: RpcMethod<P, R>,
params: P,
): R = HermesWsClient.call(method, params)
}
object RpcMethods {
val SESSION_EVENTS_SINCE: RpcMethod<SessionEventsSinceParams, SessionEventsSinceResult> =
RpcMethod(
@@ -228,6 +243,78 @@ object RpcMethods {
JsonElement.serializer(),
)
// Passthrough JsonElement results: callers keep their existing legacy parsers.
val CONNECTORS_LIST: RpcMethod<ConnectorsListParams, JsonElement> =
RpcMethod(
WsMethods.CONNECTORS_LIST,
ConnectorsListParams.serializer(),
JsonElement.serializer(),
)
// Passthrough JsonElement results: callers keep their existing legacy parsers.
val CONNECTORS_CONNECT: RpcMethod<ConnectorsConnectParams, JsonElement> =
RpcMethod(
WsMethods.CONNECTORS_CONNECT,
ConnectorsConnectParams.serializer(),
JsonElement.serializer(),
)
// Passthrough JsonElement results: callers keep their existing legacy parsers.
val CONNECTORS_OPERATION_STATUS: RpcMethod<ConnectorsOperationStatusParams, JsonElement> =
RpcMethod(
WsMethods.CONNECTORS_OPERATION_STATUS,
ConnectorsOperationStatusParams.serializer(),
JsonElement.serializer(),
)
// Passthrough JsonElement results: callers keep their existing legacy parsers.
val CONNECTORS_CATALOG: RpcMethod<ConnectorsCatalogParams, JsonElement> =
RpcMethod(
WsMethods.CONNECTORS_CATALOG,
ConnectorsCatalogParams.serializer(),
JsonElement.serializer(),
)
// Passthrough JsonElement results: callers keep their existing legacy parsers.
val CONNECTORS_ACCOUNTS: RpcMethod<ConnectorsAccountsParams, JsonElement> =
RpcMethod(
WsMethods.CONNECTORS_ACCOUNTS,
ConnectorsAccountsParams.serializer(),
JsonElement.serializer(),
)
// Passthrough JsonElement results: callers keep their existing legacy parsers.
val CONNECTORS_ACCOUNTS_REMOVE: RpcMethod<ConnectorsAccountsRemoveParams, JsonElement> =
RpcMethod(
WsMethods.CONNECTORS_ACCOUNTS_REMOVE,
ConnectorsAccountsRemoveParams.serializer(),
JsonElement.serializer(),
)
// Passthrough JsonElement results: callers keep their existing legacy parsers.
val CONNECTORS_POLICY_GET: RpcMethod<ConnectorsPolicyGetParams, JsonElement> =
RpcMethod(
WsMethods.CONNECTORS_POLICY_GET,
ConnectorsPolicyGetParams.serializer(),
JsonElement.serializer(),
)
// Passthrough JsonElement results: callers keep their existing legacy parsers.
val CONNECTORS_POLICY_SET: RpcMethod<ConnectorsPolicySetParams, JsonElement> =
RpcMethod(
WsMethods.CONNECTORS_POLICY_SET,
ConnectorsPolicySetParams.serializer(),
JsonElement.serializer(),
)
// Passthrough JsonElement results: callers keep their existing legacy parsers.
val CONNECTORS_TOOLS: RpcMethod<ConnectorsToolsParams, JsonElement> =
RpcMethod(
WsMethods.CONNECTORS_TOOLS,
ConnectorsToolsParams.serializer(),
JsonElement.serializer(),
)
/** Every typed method. GatewayContractTest iterates this; future migrations append here. */
val all: List<RpcMethod<*, *>> =
listOf(
@@ -260,5 +347,14 @@ object RpcMethods {
VAULT_SOURCE_SET,
VAULT_UNLOCK,
VAULT_LOCK,
CONNECTORS_LIST,
CONNECTORS_CONNECT,
CONNECTORS_OPERATION_STATUS,
CONNECTORS_CATALOG,
CONNECTORS_ACCOUNTS,
CONNECTORS_ACCOUNTS_REMOVE,
CONNECTORS_POLICY_GET,
CONNECTORS_POLICY_SET,
CONNECTORS_TOOLS,
)
}