feat(connectors): surface active session integrations (#1091) (#1100)

This commit is contained in:
M57 authored and GitHub committed 2026-09-13 13:41:13 +03:00
1 parent 29bc367aec
commit 52e0104cd2
23 files changed
+6579 -6

No files matched your search

@@ -0,0 +1,233 @@
package com.m57.hermescontrol.data.model
import com.m57.hermescontrol.util.ConnectorUrlValidator
/**
* High-level connection status for a connector item.
* Deliberate precedence: if [connected] is true, status resolves to [CONNECTED]
* regardless of expired/revoked/stale statuses reported by backend.
*/
enum class ConnectorStatus {
CONNECTED,
INITIATED,
EXPIRED,
REVOKED,
FAILED,
DISCONNECTED,
UNKNOWN,
;
val isConnected: Boolean get() = this == CONNECTED
companion object {
fun from(
connected: Boolean,
rawStatus: String?,
): ConnectorStatus = from(connected = connected as Boolean?, rawStatus = rawStatus)
fun from(
connected: Boolean?,
rawStatus: String?,
): ConnectorStatus {
if (connected == true) return CONNECTED
if (connected == false) {
return when (rawStatus?.lowercase()?.trim()) {
"expired" -> EXPIRED
"revoked" -> REVOKED
"initiated" -> INITIATED
"failed", "error" -> FAILED
"disconnected" -> DISCONNECTED
null, "", "active", "connected", "authorized" -> DISCONNECTED
else -> UNKNOWN
}
}
// Missing boolean: "authorized", "active", or "connected" yields effective CONNECTED
return when (rawStatus?.lowercase()?.trim()) {
"authorized", "active", "connected" -> CONNECTED
"expired" -> EXPIRED
"revoked" -> REVOKED
"initiated" -> INITIATED
"failed", "error" -> FAILED
"disconnected" -> DISCONNECTED
null, "" -> DISCONNECTED
else -> UNKNOWN
}
}
}
}
/**
* Status resulting from a connect/reconnect initiation.
*/
enum class ConnectorConnectStatus {
ACTIVE,
INITIATED,
FAILED,
UNKNOWN,
;
val isActive: Boolean get() = this == ACTIVE
companion object {
fun from(rawStatus: String?): ConnectorConnectStatus =
when (rawStatus?.lowercase()?.trim()) {
"active", "connected" -> ACTIVE
"initiated" -> INITIATED
"failed", "error" -> FAILED
null, "" -> UNKNOWN
else -> UNKNOWN
}
}
}
/**
* Individual connector item returned by `connectors.list`.
*/
data class ConnectorItem(
val connector: String,
val name: String = connector,
val connected: Boolean = false,
val enabled: Boolean = true,
val connectionStatus: String? = null,
val status: ConnectorStatus = ConnectorStatus.from(connected, connectionStatus),
val connectUrl: String? = null,
) {
val isConnected: Boolean get() = connected || status.isConnected
/** Safe validated HTTPS URL, or null if invalid or absent. */
val safeConnectUrl: String?
get() = if (ConnectorUrlValidator.isValidHttpsUrl(connectUrl)) connectUrl else null
override fun toString(): String {
val redactedUrl = if (connectUrl != null) "[REDACTED]" else "null"
return "ConnectorItem(" +
"connector=$connector, " +
"name=$name, " +
"connected=$connected, " +
"enabled=$enabled, " +
"status=$status, " +
"connectUrl=$redactedUrl" +
")"
}
}
/**
* Result of a connect/reconnect action for an individual connector.
* Instructions from arbitrary backends are intentionally not exposed for UI display.
*/
data class ConnectorConnectItem(
val connector: String,
val status: ConnectorConnectStatus,
val connectUrl: String? = null,
) {
val isConnectUrlValid: Boolean
get() = ConnectorUrlValidator.isValidHttpsUrl(connectUrl)
val safeConnectUrl: String?
get() = if (isConnectUrlValid) connectUrl else null
override fun toString(): String {
val redactedUrl = if (connectUrl != null) "[REDACTED]" else "null"
return "ConnectorConnectItem(" +
"connector=$connector, " +
"status=$status, " +
"connectUrl=$redactedUrl, " +
"isConnectUrlValid=$isConnectUrlValid" +
")"
}
}
/**
* Summary counts returned by `connectors.connect`.
*/
data class ConnectorConnectSummary(
val total: Int = 0,
val active: Int = 0,
val initiated: Int = 0,
val failed: Int = 0,
)
/**
* Typed safe connector errors mapped from RPC codes and reasons.
* Raw exceptions or auth tokens are never exposed.
*/
sealed interface ConnectorError {
val message: String
data class InvalidParams(
override val message: String = "Invalid connector parameters.",
) : ConnectorError
data class NotOwner(
override val message: String = "Session is not owned by this transport.",
) : ConnectorError
data class Unavailable(
override val message: String = "Connectors are not available in this session.",
) : ConnectorError
data class UnsupportedRuntime(
override val message: String = "Connectors must be managed on the session's compute host.",
) : ConnectorError
data class RequestFailed(
override val message: String = "Connector request failed. Please try again.",
) : ConnectorError
data class InvalidResponse(
override val message: String = "Connector service returned an invalid response.",
) : ConnectorError
data class UnsupportedBackend(
override val message: String = "Connector service is not supported by this backend.",
) : ConnectorError
data class MalformedEnvelope(
override val message: String = "Malformed response envelope received from gateway.",
) : ConnectorError
data class NetworkError(
override val message: String = "Failed to communicate with gateway.",
) : ConnectorError
data class Other(
val code: Int = 0,
override val message: String = "Connector operation failed.",
) : ConnectorError
}
/**
* Result envelope for `connectors.list`.
*/
sealed interface ConnectorListResult {
data class Success(
val available: Boolean,
val connectors: List<ConnectorItem>,
) : ConnectorListResult
data class Error(
val error: ConnectorError,
) : ConnectorListResult
fun getOrNull(): List<ConnectorItem>? = (this as? Success)?.connectors
fun errorOrNull(): ConnectorError? = (this as? Error)?.error
}
/**
* Result envelope for `connectors.connect`.
*/
sealed interface ConnectorConnectResult {
data class Success(
val results: List<ConnectorConnectItem>,
val summary: ConnectorConnectSummary,
) : ConnectorConnectResult
data class Error(
val error: ConnectorError,
) : ConnectorConnectResult
fun getOrNull(): List<ConnectorConnectItem>? = (this as? Success)?.results
fun errorOrNull(): ConnectorError? = (this as? Error)?.error
}