{-# LANGUAGE CPP #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE OverloadedStrings #-} module Database.Redis.Protocol (Reply(..), reply, renderRequest) where import Prelude hiding (error, take) #if __GLASGOW_HASKELL__ < 710 import Control.Applicative #endif import Control.DeepSeq import Scanner (Scanner) import qualified Scanner import Data.ByteString.Char8 (ByteString) import GHC.Generics import qualified Data.ByteString.Char8 as B import qualified Data.Text.Encoding as Text import qualified Data.Text.Read as Text import Control.Monad (replicateM) -- |Low-level representation of replies from the Redis server. data Reply = SingleLine ByteString | Error ByteString | Integer Integer | Bulk (Maybe ByteString) | MultiBulk (Maybe [Reply]) deriving (Eq, Show, Generic) instance NFData Reply ------------------------------------------------------------------------------ -- Request -- renderRequest :: [ByteString] -> ByteString renderRequest req = B.concat (argCnt:args) where argCnt = B.concat ["*", showBS (length req), crlf] args = map renderArg req renderArg :: ByteString -> ByteString renderArg arg = B.concat ["$", argLen arg, crlf, arg, crlf] where argLen = showBS . B.length showBS :: (Show a) => a -> ByteString showBS = B.pack . show crlf :: ByteString crlf = "\r\n" ------------------------------------------------------------------------------ -- Reply parsers -- {-# INLINE reply #-} reply :: Scanner Reply reply = do c <- Scanner.anyChar8 case c of '+' -> string '-' -> error ':' -> integer '$' -> bulk '*' -> multi _ -> fail "Unknown reply type" {-# INLINE string #-} string :: Scanner Reply string = SingleLine <$> line {-# INLINE error #-} error :: Scanner Reply error = Error <$> line {-# INLINE integer #-} integer :: Scanner Reply integer = Integer <$> integral {-# INLINE bulk #-} bulk :: Scanner Reply bulk = Bulk <$> do len <- integral if len < 0 then return Nothing else Just <$> Scanner.take len <* eol -- don't inline it to break the circle between reply and multi {-# NOINLINE multi #-} multi :: Scanner Reply multi = MultiBulk <$> do len <- integral if len < 0 then return Nothing else Just <$> replicateM len reply {-# INLINE integral #-} integral :: Integral i => Scanner i integral = do str <- line case Text.signed Text.decimal (Text.decodeUtf8 str) of Left err -> fail (show err) Right (l, _) -> return l {-# INLINE line #-} line :: Scanner ByteString line = Scanner.takeWhileChar8 (/= '\r') <* eol {-# INLINE eol #-} eol :: Scanner () eol = do Scanner.char8 '\r' Scanner.char8 '\n'