packages feed

gbnet-hs-0.1.0.0: src/GBNet/Class.hs

{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}

-- |
-- Module      : GBNet.Class
-- Description : Effect abstractions for networking
--
-- Typeclasses that abstract over network IO, enabling:
-- - Pure testing with deterministic "networks"
-- - Swappable backends (real sockets, mock, simulation)
-- - Clean separation of pure logic from IO
module GBNet.Class
  ( -- * Time
    MonoTime (..),
    MonadTime (..),
    getMonoTimeIO,

    -- * Network IO
    MonadNetwork (..),
    NetError (..),
  )
where

import Control.DeepSeq (NFData)
import Control.Monad.State.Strict (StateT (..))
import Control.Monad.Trans.Class (lift)
import Data.ByteString (ByteString)
import Data.Word (Word64)
import GHC.Clock (getMonotonicTimeNSec)
import Network.Socket (SockAddr)

-- | Monotonic time in nanoseconds.
-- Derives 'Num' because arithmetic on timestamps is pervasive.
newtype MonoTime = MonoTime {unMonoTime :: Word64}
  deriving stock (Eq, Ord, Show)
  deriving newtype (Bounded, Enum, NFData, Num)

-- | Network errors.
data NetError
  = NetSendFailed !String
  | NetSocketClosed
  | NetTimeout
  deriving (Eq, Show)

-- | Monad that can provide monotonic time.
class (Monad m) => MonadTime m where
  -- | Get current monotonic time in nanoseconds.
  getMonoTime :: m MonoTime

-- | Monad that can perform network IO.
--
-- This abstraction allows:
-- - Real IO with UDP sockets
-- - Pure testing with simulated networks
-- - Network condition simulation (latency, loss, reordering)
class (MonadTime m) => MonadNetwork m where
  -- | Send raw bytes to an address.
  -- Returns Left on failure, Right () on success.
  netSend :: SockAddr -> ByteString -> m (Either NetError ())

  -- | Receive bytes (non-blocking).
  -- Returns Nothing if no data available, Just (data, sender) otherwise.
  netRecv :: m (Maybe (ByteString, SockAddr))

  -- | Close the network (cleanup).
  netClose :: m ()

-- | Lift MonadTime through StateT.
instance (MonadTime m) => MonadTime (StateT s m) where
  getMonoTime = lift getMonoTime

-- | Lift MonadNetwork through StateT.
instance (MonadNetwork m) => MonadNetwork (StateT s m) where
  netSend addr bs = lift (netSend addr bs)
  netRecv = lift netRecv
  netClose = lift netClose

-- | Get current monotonic time in nanoseconds (IO helper).
getMonoTimeIO :: IO MonoTime
getMonoTimeIO = MonoTime <$> getMonotonicTimeNSec

-- | MonadTime instance for IO.
instance MonadTime IO where
  getMonoTime = getMonoTimeIO