freckle-app-1.9.1.0: library/Freckle/App/Memcached/Servers.hs
{-# LANGUAGE CPP #-}
-- | Read a Memcached Servers value, to support ENV-based configuration
--
-- Format:
--
-- @
-- memcached://[user[:password]@]host][:port],...
-- @
--
-- Usage with "Freckle.App.Env":
--
-- @
-- -- Required
-- Env.var (Env.eitherReader readMemcachedServers <=< Env.nonempty) "MEMCACHED_SERVERS" mempty
--
-- -- Default to localhost:11211
-- Env.var (Env.eitherReader readMemcachedServers) "MEMCACHED_SERVERS" (Env.def defaultMemcachedServers)
--
-- -- Default to disabled
-- Env.var (Env.eitherReader readMemcachedServers) "MEMCACHED_SERVERS" (Env.def emptyMemcachedServers)
-- @
module Freckle.App.Memcached.Servers
( MemcachedServers (..)
, defaultMemcachedServers
, emptyMemcachedServers
, readMemcachedServers
, toServerSpecs
) where
import Freckle.App.Prelude
import Control.Error.Util (note)
import qualified Data.Text as T
import qualified Database.Memcache.Client as Memcache
import Network.URI (URI (..), URIAuth (..), parseAbsoluteURI)
newtype MemcachedServers = MemcachedServers
{ unMemcachedServers :: [MemcachedServer]
}
defaultMemcachedServers :: MemcachedServers
defaultMemcachedServers = MemcachedServers [defaultMemcachedServer]
emptyMemcachedServers :: MemcachedServers
emptyMemcachedServers = MemcachedServers []
readMemcachedServers :: String -> Either String MemcachedServers
readMemcachedServers =
fmap MemcachedServers
. traverse (readMemcachedServer . unpack)
. filter (not . T.null)
. map T.strip
. T.splitOn ","
. pack
toServerSpecs :: MemcachedServers -> [Memcache.ServerSpec]
toServerSpecs = map unMemcachedServer . unMemcachedServers
newtype MemcachedServer = MemcachedServer
{ unMemcachedServer :: Memcache.ServerSpec
}
defaultMemcachedServer :: MemcachedServer
defaultMemcachedServer = MemcachedServer Memcache.def
readMemcachedServer :: String -> Either String MemcachedServer
readMemcachedServer s = do
uri <- note ("Not a valid URI: " <> s) $ parseAbsoluteURI s
note "Must begin memcached://" $ guard $ uriScheme uri == "memcached:"
let mAuth = uriAuthority uri
pure
. MemcachedServer
. maybe id setHost mAuth
. maybe id setPort mAuth
. maybe id setAuth (readAuthentication . uriUserInfo =<< mAuth)
$ Memcache.def
readAuthentication :: String -> Maybe Memcache.Authentication
readAuthentication = go . pack
where
go a = do
(u, p) <- second (T.drop 1) . T.breakOn ":" <$> T.stripSuffix "@" a
guard $ not $ T.null u
guard $ not $ T.null p
pure
Memcache.Auth
{ Memcache.username = encodeUtf8 u
, Memcache.password = encodeUtf8 p
}
setHost :: URIAuth -> Memcache.ServerSpec -> Memcache.ServerSpec
setHost auth ss = case uriRegName auth of
"" -> ss
rn -> ss {Memcache.ssHost = rn}
setPort :: URIAuth -> Memcache.ServerSpec -> Memcache.ServerSpec
setPort auth ss = fromMaybe ss $ do
p <- case uriPort auth of
"" -> Nothing
(':' : p) -> fromPort p
p -> fromPort p
pure $ ss {Memcache.ssPort = p}
where
#if MIN_VERSION_memcache(0,3,0)
-- ssPort is a ServiceName, which is a String
fromPort = Just
#else
-- ssPort is a PortNumber, which we need to Read
fromPort = readMay
#endif
setAuth
:: Memcache.Authentication -> Memcache.ServerSpec -> Memcache.ServerSpec
setAuth auth ss = ss {Memcache.ssAuth = auth}