packages feed

tmp-proc-redis-0.7.2.5: src/System/TmpProc/Docker/Redis.hs

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# OPTIONS_HADDOCK prune not-home #-}

{- |
Copyright   : (c) 2020-2021 Tim Emiola
SPDX-License-Identifier: BSD3
Maintainer  : Tim Emiola <adetokunbo@users.noreply.github.com >

Provides an instance of 'Proc' that launches @redis@ as a @tmp proc@.

The instance this module provides can be used in integration tests as is.

It's also possible to write other instances that launch @redis@ in different
ways; for those, this instance can be used as a reference example.
-}
module System.TmpProc.Docker.Redis
  ( -- * 'Proc' instance
    TmpRedis (..)

    -- * Useful definitions
  , aProc
  , aHandle
  , KeyName

    -- * Re-exports
  , module System.TmpProc
  , del
  )
where

import Control.Exception (catch)
import qualified Data.ByteString.Char8 as C8
import qualified Data.Text as Text
import Database.Redis
  ( ConnectTimeout
  , Connection
  , checkedConnect
  , disconnect
  , parseConnectInfo
  , runRedis
  )
import Database.Redis.CPP (del)
import System.TmpProc
  ( Connectable (..)
  , HList (..)
  , HandlesOf
  , HostIpAddress
  , Pinged (..)
  , Proc (..)
  , ProcHandle (..)
  , SvcURI
  , only
  , startupAll
  , withTmpConn
  )


-- | A singleton 'HList' containing an example 'TmpRedis'.
aProc :: HList '[TmpRedis]
aProc = only $ TmpRedis []


-- | An 'HList' that just contains the handle created from 'aProc'.
aHandle :: IO (HandlesOf '[TmpRedis])
aHandle = startupAll aProc


-- | The name of a key in redis.
type KeyName = C8.ByteString


{- | Provides the capability to launch a redis instance as @tmp proc@.

The constructor receives the names of keys to be dropped on 'reset'.
-}
newtype TmpRedis = TmpRedis [KeyName]


-- | Specifies how to run @redis@ as a @tmp proc@.
instance Proc TmpRedis where
  type Image TmpRedis = "redis:5.0"
  type Name TmpRedis = "a-redis-db"
  uriOf = mkUri'
  runArgs = []
  ping = toPinged . flip withTmpConn (const $ pure ())
  reset = clearKeys


-- | Specifies how to connect to a tmp @redis@ service.
instance Connectable TmpRedis where
  type Conn TmpRedis = Connection
  closeConn = disconnect
  openConn = openConn'


openConn' :: ProcHandle TmpRedis -> IO Connection
openConn' handle = case parseConnectInfo $ C8.unpack $ hUri handle of
  Left _ -> fail $ "invalid redis uri: " ++ C8.unpack (hUri handle)
  Right x -> checkedConnect x


toPinged :: IO a -> IO Pinged
toPinged action =
  ( (action >> pure OK)
      `catch` (\(_ :: ConnectTimeout) -> pure NotOK)
  )
    `catch` (\(_ :: IOError) -> pure NotOK)


mkUri' :: HostIpAddress -> SvcURI
mkUri' ip = "redis://" <> C8.pack (Text.unpack ip) <> "/"


clearKeys :: ProcHandle TmpRedis -> IO ()
clearKeys handle@ProcHandle {hProc} =
  let go (TmpRedis xs) = withTmpConn handle $ \c -> runRedis c $ del xs
   in go hProc