packages feed

synapse-cc-0.4.0: src/SynapseCC/RegistryResolve.hs

{-# LANGUAGE OverloadedStrings #-}

-- | Registry-based backend address resolution (SAFE-3).
--
-- synapse-cc previously assumed every backend lived at the registry's
-- own host:port (typically @127.0.0.1:4444@), which broke for backends
-- like @lforge@ that live on different ports. This module queries the
-- Plexus registry to map a backend name to its actual host:port.
--
-- The registry's location defaults to @127.0.0.1:4444@; pass
-- @--host@\/@--port@ to override.
--
-- Resolution flow:
--
--  1. Query @registry.get@ at the given registry address for the named backend.
--  2. If found, return the registered host:port.
--  3. If the backend is not registered, list known backends and fail.
--  4. If the registry itself is unreachable, fall back to the registry
--     address as the backend address (preserves the legacy "substrate IS
--     the registry on 4444" path) and warn.
module SynapseCC.RegistryResolve
  ( ResolveResult(..)
  , resolveBackendAddress
  , resolveRegistryEndpoint
  ) where

import Control.Exception (try, SomeException)
import Data.Text (Text)
import qualified Data.Text as T
import Text.Read (readMaybe)

import qualified Synapse.Backend.Discovery as D

-- | Outcome of resolving a backend name against the registry.
data ResolveResult
  = ResolvedFromRegistry  !Text !Int  -- ^ Backend found in registry: returned host, port
  | ResolvedFallback      !Text !Int  -- ^ Registry unreachable: falling back to provided address
  | NotInRegistry         ![Text]     -- ^ Backend not in registry; suggestions = known backend names
  deriving stock (Show, Eq)

-- | Z2H-6: resolve the effective registry endpoint from the CLI flags.
--
-- When @--host@/@--port@ sit at their defaults (@127.0.0.1@/@4444@),
-- the @PLEXUS_REGISTRY_URL@ env var (shared with plexus-transport's
-- boot-time self-registration and synapse) may point the registry
-- elsewhere. Explicit non-default flags always win.
resolveRegistryEndpoint
  :: Text  -- ^ cfg host (CLI --host, default "127.0.0.1")
  -> Text  -- ^ cfg port (CLI --port, default "4444")
  -> IO (Text, Int)
resolveRegistryEndpoint cfgHost cfgPortText = do
  let cfgPort = maybe D.defaultRegistryPort id (readMaybe (T.unpack cfgPortText))
  if cfgHost == D.defaultRegistryHost && cfgPort == D.defaultRegistryPort
    then D.registryEndpointFromEnv
    else pure (cfgHost, cfgPort)

-- | Resolve a backend name to a host:port using the registry at the
-- provided address. Always returns SOMETHING — never throws.
resolveBackendAddress
  :: Text   -- ^ registry host (also fallback)
  -> Int    -- ^ registry port (also fallback)
  -> Text   -- ^ backend name to resolve
  -> IO ResolveResult
resolveBackendAddress regHost regPort bkName = do
  let discovery = D.registryDiscovery regHost regPort
  -- Try to look up the named backend.
  result <- try (D.getBackendInfo discovery bkName) :: IO (Either SomeException (Maybe D.Backend))
  case result of
    Right (Just b) ->
      pure $ ResolvedFromRegistry (D.backendHost b) (D.backendPort b)
    Right Nothing -> do
      -- Backend not in registry. Try to list registered ones for a helpful error.
      knownE <- try (D.discoverBackends discovery) :: IO (Either SomeException [D.Backend])
      let known = case knownE of
            Right bs -> map D.backendName bs
            Left _   -> []
      pure $ NotInRegistry known
    Left _ ->
      -- Registry call failed (registry unreachable or RPC error).
      -- Fall back to the registry address as the backend address —
      -- preserves the "substrate is reachable on the same port" legacy path.
      pure $ ResolvedFallback regHost regPort