hw-kafka-client 2.5.0 → 2.6.0
raw patch · 27 files changed
+640/−370 lines, 27 filesdep +text
Dependencies added: text
Files
- README.md +19/−5
- example/ConsumerExample.hs +12/−3
- example/ProducerExample.hs +10/−3
- hw-kafka-client.cabal +12/−8
- src/Kafka/Callbacks.hs +3/−3
- src/Kafka/Consumer.hs +64/−15
- src/Kafka/Consumer/Callbacks.hs +15/−14
- src/Kafka/Consumer/ConsumerProperties.hs +29/−14
- src/Kafka/Consumer/Convert.hs +46/−14
- src/Kafka/Consumer/Subscription.hs +23/−12
- src/Kafka/Consumer/Types.hs +52/−17
- src/Kafka/Dump.hs +30/−14
- src/Kafka/Internal/CancellationToken.hs +7/−1
- src/Kafka/Internal/RdKafka.chs +24/−12
- src/Kafka/Internal/Setup.hs +45/−19
- src/Kafka/Internal/Shared.hs +32/−10
- src/Kafka/Metadata.hs +48/−43
- src/Kafka/Producer.hs +29/−16
- src/Kafka/Producer/Callbacks.hs +9/−8
- src/Kafka/Producer/Convert.hs +11/−6
- src/Kafka/Producer/ProducerProperties.hs +28/−14
- src/Kafka/Producer/Types.hs +17/−27
- src/Kafka/Types.hs +56/−45
- tests-it/Kafka/IntegrationSpec.hs +5/−3
- tests-it/Kafka/TestEnv.hs +6/−7
- tests/Kafka/Consumer/ConsumerRecordMapSpec.hs +4/−3
- tests/Kafka/Consumer/ConsumerRecordTraverseSpec.hs +4/−34
README.md view
@@ -33,6 +33,7 @@ To run an example please compile with the `examples` flag. ```Haskell+import Control.Exception (bracket) import Data.Monoid ((<>)) import Kafka import Kafka.Consumer@@ -52,8 +53,14 @@ -- Running an example runConsumerExample :: IO () runConsumerExample = do- res <- runConsumer consumerProps consumerSub processMessages+ res <- bracket mkConsumer clConsumer runHandler print res+ where+ mkConsumer = newConsumer consumerProps consumerSub+ clConsumer (Left err) = return (Left err)+ clConsumer (Right kc) = (maybe (Right ()) Left) <$> closeConsumer kc+ runHandler (Left err) = return (Left err)+ runHandler (Right kc) = processMessages kc ------------------------------------------------------------------- processMessages :: KafkaConsumer -> IO (Either KafkaError ())@@ -111,8 +118,10 @@ ### Example ```Haskell+{-# LANGUAGE OverloadedStrings #-}+import Control.Exception (bracket) import Control.Monad (forM_)-import Kafka+import Data.ByteString (ByteString) import Kafka.Producer -- Global producer properties@@ -126,9 +135,14 @@ -- Run an example runProducerExample :: IO ()-runProducerExample = do- res <- runProducer producerProps sendMessages- print res+runProducerExample =+ bracket mkProducer clProducer runHandler >>= print+ where+ mkProducer = newProducer producerProps+ clProducer (Left _) = return ()+ clProducer (Right prod) = closeProducer prod+ runHandler (Left err) = return $ Left err+ runHandler (Right prod) = sendMessages prod sendMessages :: KafkaProducer -> IO (Either KafkaError ()) sendMessages prod = do
example/ConsumerExample.hs view
@@ -1,11 +1,14 @@+{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} module ConsumerExample where -import Control.Arrow ((&&&))-import Data.Monoid ((<>))+import Control.Arrow ((&&&))+import Control.Exception (bracket)+import Data.Monoid ((<>)) import Kafka.Consumer+import Data.Text (Text) -- Global consumer properties consumerProps :: ConsumerProperties@@ -25,8 +28,14 @@ runConsumerExample :: IO () runConsumerExample = do print $ cpLogLevel consumerProps- res <- runConsumer consumerProps consumerSub processMessages+ res <- bracket mkConsumer clConsumer runHandler print res+ where+ mkConsumer = newConsumer consumerProps consumerSub+ clConsumer (Left err) = return (Left err)+ clConsumer (Right kc) = (maybe (Right ()) Left) <$> closeConsumer kc+ runHandler (Left err) = return (Left err)+ runHandler (Right kc) = processMessages kc ------------------------------------------------------------------- processMessages :: KafkaConsumer -> IO (Either KafkaError ())
example/ProducerExample.hs view
@@ -3,11 +3,13 @@ module ProducerExample where +import Control.Exception (bracket) import Control.Monad (forM_) import Data.ByteString (ByteString) import Data.ByteString.Char8 (pack) import Data.Monoid import Kafka.Producer+import Data.Text (Text) -- Global producer properties producerProps :: ProducerProperties@@ -30,9 +32,14 @@ -- Run an example runProducerExample :: IO ()-runProducerExample = do- res <- runProducer producerProps sendMessages- print res+runProducerExample =+ bracket mkProducer clProducer runHandler >>= print+ where+ mkProducer = newProducer producerProps+ clProducer (Left _) = return ()+ clProducer (Right prod) = closeProducer prod+ runHandler (Left err) = return $ Left err+ runHandler (Right prod) = sendMessages prod sendMessages :: KafkaProducer -> IO (Either KafkaError ()) sendMessages prod = do
hw-kafka-client.cabal view
@@ -1,11 +1,5 @@--- This file has been generated from package.yaml by hpack version 0.28.2.------ see: https://github.com/sol/hpack------ hash: c01d07e897b9cf5d0f6237227a00230ec841fe4a3a22766142dc015f4f703ba1- name: hw-kafka-client-version: 2.5.0+version: 2.6.0 synopsis: Kafka bindings for Haskell description: Apache Kafka bindings backed by the librdkafka C library. .@@ -40,7 +34,12 @@ library hs-source-dirs: src- ghc-options: -Wall -Wcompat -Wincomplete-record-updates -Wincomplete-uni-patterns -Wredundant-constraints+ ghc-options:+ -Wall+ -Wcompat+ -Wincomplete-record-updates+ -Wincomplete-uni-patterns+ -Wredundant-constraints extra-libraries: rdkafka build-depends:@@ -48,6 +47,7 @@ , bifunctors , bytestring , containers+ , text , transformers , unix build-tools:@@ -92,6 +92,7 @@ , bytestring , containers , hw-kafka-client+ , text , transformers , unix if !(flag(examples))@@ -117,6 +118,8 @@ , hspec , hw-kafka-client , monad-loops+ , text+ , transformers other-modules: Kafka.IntegrationSpec Kafka.TestEnv@@ -137,6 +140,7 @@ , either , hspec , hw-kafka-client+ , text , monad-loops other-modules: Kafka.Consumer.ConsumerRecordMapSpec
src/Kafka/Callbacks.hs view
@@ -5,9 +5,9 @@ ) where -import Kafka.Internal.RdKafka-import Kafka.Internal.Setup-import Kafka.Types+import Kafka.Internal.RdKafka (rdKafkaConfSetErrorCb, rdKafkaConfSetLogCb, rdKafkaConfSetStatsCb)+import Kafka.Internal.Setup (HasKafkaConf(..), getRdKafkaConf)+import Kafka.Types (KafkaError(..)) errorCallback :: HasKafkaConf k => (KafkaError -> String -> IO ()) -> k -> IO () errorCallback callback k =
src/Kafka/Consumer.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TupleSections #-} module Kafka.Consumer ( module X@@ -17,25 +18,73 @@ ) where -import Control.Arrow+import Data.Set (Set)+import qualified Data.Set as Set+import qualified Data.Text as Text+import Control.Arrow ((&&&), left) import Control.Concurrent (forkIO, rtsSupportsBoundThreads)-import Control.Exception+import Control.Exception (bracket) import Control.Monad (forM_, void, when)-import Control.Monad.IO.Class-import Control.Monad.Trans.Except-import Data.Bifunctor+import Control.Monad.IO.Class (MonadIO(liftIO))+import Control.Monad.Trans.Except (ExceptT(ExceptT), runExceptT)+import Data.Bifunctor (first, bimap) import qualified Data.ByteString as BS-import Data.IORef+import Data.IORef (writeIORef, readIORef) import qualified Data.Map as M import Data.Maybe (fromMaybe) import Data.Monoid ((<>)) import Foreign hiding (void) import Kafka.Consumer.Convert-import Kafka.Consumer.Types+ (fromMessagePtr, toNativeTopicPartitionList, topicPartitionFromMessageForCommit+ , toNativeTopicPartitionListNoDispose, toNativeTopicPartitionList, fromNativeTopicPartitionList''+ , toMap, offsetToInt64, toNativeTopicPartitionList', offsetCommitToBool+ )+import Kafka.Consumer.Types (KafkaConsumer(..)) import Kafka.Internal.CancellationToken as CToken import Kafka.Internal.RdKafka+ ( RdKafkaRespErrT(..)+ , RdKafkaTopicPartitionListTPtr+ , RdKafkaTypeT(..)+ , newRdKafkaT+ , rdKafkaQueueNew+ , rdKafkaConsumeQueue+ , rdKafkaPollSetConsumer+ , rdKafkaSetLogLevel+ , rdKafkaOffsetsStore+ , rdKafkaCommit+ , rdKafkaConfSetDefaultTopicConf+ , rdKafkaTopicConfDup+ , rdKafkaSubscribe+ , rdKafkaTopicPartitionListAdd+ , newRdKafkaTopicPartitionListT+ , rdKafkaConsumerClose+ , rdKafkaQueueDestroy+ , rdKafkaConsumerPoll+ , rdKafkaPosition+ , rdKafkaCommitted+ , rdKafkaSeek+ , rdKafkaResumePartitions+ , rdKafkaPausePartitions+ , rdKafkaSubscription+ , rdKafkaAssignment+ , rdKafkaConsumeBatchQueue+ , newRdKafkaTopicT+ ) import Kafka.Internal.Setup+ ( Kafka(..)+ , KafkaConf(..)+ , TopicConf(..)+ , KafkaProps(..)+ , TopicProps(..)+ , kafkaConf+ , topicConf+ , getRdKafka+ ) import Kafka.Internal.Shared+ ( kafkaErrorToMaybe+ , maybeToLeft+ , rdKafkaErrorToEither+ ) import Kafka.Consumer.ConsumerProperties as X import Kafka.Consumer.Subscription as X@@ -67,7 +116,7 @@ newConsumer props (Subscription ts tp) = liftIO $ do let cp = setCallback (rebalanceCallback (\_ _ -> return ())) <> props kc@(KafkaConf kc' qref ct) <- newConsumerConf cp- tp' <- topicConf (TopicProps $ M.toList tp)+ tp' <- topicConf (TopicProps tp) _ <- setDefaultTopicConf kc tp' rdk <- newRdKafkaT RdKafkaConsumer kc' case rdk of@@ -179,14 +228,14 @@ pausePartitions :: MonadIO m => KafkaConsumer -> [(TopicName, PartitionId)] -> m KafkaError pausePartitions (KafkaConsumer (Kafka k) _) ps = liftIO $ do pl <- newRdKafkaTopicPartitionListT (length ps)- mapM_ (\(TopicName topicName, PartitionId partitionId) -> rdKafkaTopicPartitionListAdd pl topicName partitionId) ps+ mapM_ (\(TopicName topicName, PartitionId partitionId) -> rdKafkaTopicPartitionListAdd pl (Text.unpack topicName) partitionId) ps KafkaResponseError <$> rdKafkaPausePartitions k pl -- | Resumes specified partitions on the current consumer. resumePartitions :: MonadIO m => KafkaConsumer -> [(TopicName, PartitionId)] -> m KafkaError resumePartitions (KafkaConsumer (Kafka k) _) ps = liftIO $ do pl <- newRdKafkaTopicPartitionListT (length ps)- mapM_ (\(TopicName topicName, PartitionId partitionId) -> rdKafkaTopicPartitionListAdd pl topicName partitionId) ps+ mapM_ (\(TopicName topicName, PartitionId partitionId) -> rdKafkaTopicPartitionListAdd pl (Text.unpack topicName) partitionId) ps KafkaResponseError <$> rdKafkaResumePartitions k pl seek :: MonadIO m => KafkaConsumer -> Timeout -> [TopicPartition] -> m (Maybe KafkaError)@@ -203,8 +252,8 @@ topicPair tp = do let (TopicName tn) = tpTopicName tp- nt <- newRdKafkaTopicT k tn Nothing- return $ bimap KafkaError (,tpPartition tp, tpOffset tp) nt+ nt <- newRdKafkaTopicT k (Text.unpack tn) Nothing+ return $ bimap KafkaError (,tpPartition tp, tpOffset tp) (first Text.pack nt) -- | Retrieve committed offsets for topics+partitions. committed :: MonadIO m => KafkaConsumer -> Timeout -> [(TopicName, PartitionId)] -> m (Either KafkaError [TopicPartition])@@ -256,7 +305,7 @@ ----------------------------------------------------------------------------- newConsumerConf :: ConsumerProperties -> IO KafkaConf newConsumerConf ConsumerProperties {cpProps = m, cpCallbacks = cbs} = do- conf <- kafkaConf (KafkaProps $ M.toList m)+ conf <- kafkaConf (KafkaProps m) forM_ cbs (\setCb -> setCb conf) return conf @@ -266,10 +315,10 @@ -- any topic name in the topics list that is prefixed with @^@ will -- be regex-matched to the full list of topics in the cluster and matching -- topics will be added to the subscription list.-subscribe :: KafkaConsumer -> [TopicName] -> IO (Maybe KafkaError)+subscribe :: KafkaConsumer -> Set TopicName -> IO (Maybe KafkaError) subscribe (KafkaConsumer (Kafka k) _) ts = do pl <- newRdKafkaTopicPartitionListT (length ts)- mapM_ (\(TopicName t) -> rdKafkaTopicPartitionListAdd pl t (-1)) ts+ mapM_ (\(TopicName t) -> rdKafkaTopicPartitionListAdd pl (Text.unpack t) (-1)) (Set.toList ts) res <- KafkaResponseError <$> rdKafkaSubscribe k pl return $ kafkaErrorToMaybe res
src/Kafka/Consumer/Callbacks.hs view
@@ -6,19 +6,20 @@ ) where -import Control.Arrow ((&&&))-import Control.Monad (forM_, void)-import Data.Monoid ((<>))-import Foreign hiding (void)-import Kafka.Callbacks as X-import Kafka.Consumer.Convert-import Kafka.Consumer.Types-import Kafka.Internal.RdKafka-import Kafka.Internal.Setup-import Kafka.Internal.Shared-import Kafka.Types--import Control.Concurrent (threadDelay)+import Control.Arrow ((&&&))+import Control.Concurrent (threadDelay)+import Control.Monad (forM_, void)+import Data.Monoid ((<>))+import qualified Data.Text as Text+import Foreign.ForeignPtr (newForeignPtr_)+import Foreign.Ptr (nullPtr)+import Kafka.Callbacks as X+import Kafka.Consumer.Convert (fromNativeTopicPartitionList', fromNativeTopicPartitionList'', toNativeTopicPartitionList)+import Kafka.Consumer.Types (KafkaConsumer(..), TopicPartition(..), RebalanceEvent(..))+import Kafka.Internal.RdKafka+import Kafka.Internal.Setup (Kafka(..), KafkaConf(..), HasKafka(..), HasKafkaConf(..), getRdMsgQueue)+import Kafka.Internal.Shared (kafkaErrorToMaybe)+import Kafka.Types (KafkaError(..), PartitionId(..), TopicName(..)) -- | Sets a callback that is called when rebalance is needed. --@@ -62,7 +63,7 @@ ------------------------------------------------------------------------------- redirectPartitionQueue :: Kafka -> TopicName -> PartitionId -> RdKafkaQueueTPtr -> IO () redirectPartitionQueue (Kafka k) (TopicName t) (PartitionId p) q = do- mpq <- rdKafkaQueueGetPartition k t p+ mpq <- rdKafkaQueueGetPartition k (Text.unpack t) p case mpq of Nothing -> return () Just pq -> rdKafkaQueueForward pq q
src/Kafka/Consumer/ConsumerProperties.hs view
@@ -1,24 +1,39 @@+{-# LANGUAGE OverloadedStrings #-}+ module Kafka.Consumer.ConsumerProperties-( module Kafka.Consumer.ConsumerProperties+( ConsumerProperties(..)+, brokersList+, noAutoCommit+, noAutoOffsetStore+, groupId+, clientId+, setCallback+, logLevel+, compression+, suppressDisconnectLogs+, extraProps+, extraProp+, debugOptions+, queuedMaxMessagesKBytes , module X ) where ----import Control.Monad-import qualified Data.List as L+import Control.Monad (MonadPlus(mplus)) import Data.Map (Map) import qualified Data.Map as M import Data.Semigroup as Sem-import Kafka.Consumer.Types-import Kafka.Internal.Setup-import Kafka.Types+import Data.Text (Text)+import qualified Data.Text as Text+import Kafka.Consumer.Types (ConsumerGroupId(..))+import Kafka.Internal.Setup (KafkaConf(..))+import Kafka.Types (KafkaDebug(..), KafkaCompressionCodec(..), KafkaLogLevel(..), ClientId(..), BrokerAddress(..), kafkaDebugToText, kafkaCompressionCodecToText) import Kafka.Consumer.Callbacks as X -- | Properties to create 'KafkaConsumer'. data ConsumerProperties = ConsumerProperties- { cpProps :: Map String String+ { cpProps :: Map Text Text , cpLogLevel :: Maybe KafkaLogLevel , cpCallbacks :: [KafkaConf -> IO ()] }@@ -41,7 +56,7 @@ brokersList :: [BrokerAddress] -> ConsumerProperties brokersList bs =- let bs' = L.intercalate "," ((\(BrokerAddress x) -> x) <$> bs)+ let bs' = Text.intercalate "," ((\(BrokerAddress x) -> x) <$> bs) in extraProps $ M.fromList [("bootstrap.servers", bs')] -- | Disables auto commit for the consumer@@ -74,7 +89,7 @@ -- | Sets the compression codec for the consumer. compression :: KafkaCompressionCodec -> ConsumerProperties compression c =- extraProps $ M.singleton "compression.codec" (kafkaCompressionCodecToString c)+ extraProps $ M.singleton "compression.codec" (kafkaCompressionCodecToText c) -- | Suppresses consumer disconnects logs. --@@ -86,13 +101,13 @@ -- | Any configuration options that are supported by /librdkafka/. -- The full list can be found <https://github.com/edenhill/librdkafka/blob/master/CONFIGURATION.md here>-extraProps :: Map String String -> ConsumerProperties+extraProps :: Map Text Text -> ConsumerProperties extraProps m = mempty { cpProps = m } {-# INLINE extraProps #-} -- | Any configuration options that are supported by /librdkafka/. -- The full list can be found <https://github.com/edenhill/librdkafka/blob/master/CONFIGURATION.md here>-extraProp :: String -> String -> ConsumerProperties+extraProp :: Text -> Text -> ConsumerProperties extraProp k v = mempty { cpProps = M.singleton k v } {-# INLINE extraProp #-} @@ -101,10 +116,10 @@ debugOptions :: [KafkaDebug] -> ConsumerProperties debugOptions [] = extraProps M.empty debugOptions d =- let points = L.intercalate "," (kafkaDebugToString <$> d)+ let points = Text.intercalate "," (kafkaDebugToText <$> d) in extraProps $ M.fromList [("debug", points)] queuedMaxMessagesKBytes :: Int -> ConsumerProperties queuedMaxMessagesKBytes kBytes =- extraProp "queued.max.messages.kbytes" (show kBytes)+ extraProp "queued.max.messages.kbytes" (Text.pack $ show kBytes) {-# INLINE queuedMaxMessagesKBytes #-}
src/Kafka/Consumer/Convert.hs view
@@ -1,18 +1,48 @@ module Kafka.Consumer.Convert-+( offsetSyncToInt+, offsetToInt64+, int64ToOffset+, fromNativeTopicPartitionList''+, fromNativeTopicPartitionList'+, fromNativeTopicPartitionList+, toNativeTopicPartitionList+, toNativeTopicPartitionListNoDispose+, toNativeTopicPartitionList'+, topicPartitionFromMessage+, topicPartitionFromMessageForCommit+, toMap+, fromMessagePtr+, offsetCommitToBool+) where -import Control.Monad+import Control.Monad ((>=>)) import qualified Data.ByteString as BS+import Data.Int (Int64) import Data.Map.Strict (Map, fromListWith) import qualified Data.Set as S-import Foreign-import Foreign.C.Error-import Foreign.C.String-import Kafka.Consumer.Types+import qualified Data.Text as Text+import Foreign.Ptr (Ptr, nullPtr)+import Foreign.ForeignPtr (withForeignPtr)+import Foreign.Storable (Storable(..))+import Foreign.C.Error (getErrno)+import Kafka.Consumer.Types (ConsumerRecord(..), TopicPartition(..), Offset(..), OffsetCommit(..), PartitionOffset(..), OffsetStoreSync(..)) import Kafka.Internal.RdKafka-import Kafka.Internal.Shared-import Kafka.Types+ ( RdKafkaRespErrT(..)+ , RdKafkaMessageT(..)+ , RdKafkaTopicPartitionListTPtr+ , RdKafkaTopicPartitionListT(..)+ , RdKafkaMessageTPtr+ , RdKafkaTopicPartitionT(..)+ , rdKafkaTopicPartitionListAdd+ , newRdKafkaTopicPartitionListT+ , rdKafkaMessageDestroy+ , rdKafkaTopicPartitionListSetOffset+ , rdKafkaTopicPartitionListNew+ , peekCText+ )+import Kafka.Internal.Shared (kafkaRespErr, readTopic, readKey, readPayload, readTimestamp)+import Kafka.Types (KafkaError(..), PartitionId(..), TopicName(..)) -- | Converts offsets sync policy to integer (the way Kafka understands it): --@@ -65,7 +95,7 @@ where toPart :: RdKafkaTopicPartitionT -> IO TopicPartition toPart p = do- topic <- peekCString $ topic'RdKafkaTopicPartitionT p+ topic <- peekCText $ topic'RdKafkaTopicPartitionT p return TopicPartition { tpTopicName = TopicName topic, tpPartition = PartitionId $ partition'RdKafkaTopicPartitionT p,@@ -79,8 +109,9 @@ let TopicName tn = tpTopicName p (PartitionId tp) = tpPartition p to = offsetToInt64 $ tpOffset p- _ <- rdKafkaTopicPartitionListAdd pl tn tp- rdKafkaTopicPartitionListSetOffset pl tn tp to) ps+ tnS = Text.unpack tn + _ <- rdKafkaTopicPartitionListAdd pl tnS tp+ rdKafkaTopicPartitionListSetOffset pl tnS tp to) ps return pl toNativeTopicPartitionListNoDispose :: [TopicPartition] -> IO RdKafkaTopicPartitionListTPtr@@ -90,15 +121,16 @@ let TopicName tn = tpTopicName p (PartitionId tp) = tpPartition p to = offsetToInt64 $ tpOffset p- _ <- rdKafkaTopicPartitionListAdd pl tn tp- rdKafkaTopicPartitionListSetOffset pl tn tp to) ps+ tnS = Text.unpack tn + _ <- rdKafkaTopicPartitionListAdd pl tnS tp+ rdKafkaTopicPartitionListSetOffset pl tnS tp to) ps return pl toNativeTopicPartitionList' :: [(TopicName, PartitionId)] -> IO RdKafkaTopicPartitionListTPtr toNativeTopicPartitionList' tps = do let utps = S.toList . S.fromList $ tps pl <- newRdKafkaTopicPartitionListT (length utps)- mapM_ (\(TopicName t, PartitionId p) -> rdKafkaTopicPartitionListAdd pl t p) utps+ mapM_ (\(TopicName t, PartitionId p) -> rdKafkaTopicPartitionListAdd pl (Text.unpack t) p) utps return pl topicPartitionFromMessage :: ConsumerRecord k v -> TopicPartition
src/Kafka/Consumer/Subscription.hs view
@@ -1,44 +1,55 @@+{-# LANGUAGE OverloadedStrings #-}+ module Kafka.Consumer.Subscription+( Subscription(..)+, topics+, offsetReset+, autoCommit+, extraSubscriptionProps+) where -import qualified Data.List as L+import qualified Data.Text as Text+import Data.Text (Text) import Data.Map (Map) import qualified Data.Map as M import Data.Semigroup as Sem-import Kafka.Consumer.Types-import Kafka.Types+import Kafka.Consumer.Types (OffsetReset(..))+import Kafka.Types (TopicName(..), Millis(..))+import Data.Set (Set)+import qualified Data.Set as Set -data Subscription = Subscription [TopicName] (Map String String)+data Subscription = Subscription (Set TopicName) (Map Text Text) instance Sem.Semigroup Subscription where (Subscription ts1 m1) <> (Subscription ts2 m2) =- let ts' = L.nub $ L.union ts1 ts2+ let ts' = Set.union ts1 ts2 ps' = M.union m1 m2 in Subscription ts' ps' {-# INLINE (<>) #-} instance Monoid Subscription where- mempty = Subscription [] M.empty+ mempty = Subscription Set.empty M.empty {-# INLINE mempty #-} mappend = (Sem.<>) {-# INLINE mappend #-} topics :: [TopicName] -> Subscription-topics ts = Subscription (L.nub ts) M.empty+topics ts = Subscription (Set.fromList ts) M.empty offsetReset :: OffsetReset -> Subscription offsetReset o = let o' = case o of Earliest -> "earliest" Latest -> "latest"- in Subscription [] (M.fromList [("auto.offset.reset", o')])+ in Subscription (Set.empty) (M.fromList [("auto.offset.reset", o')]) autoCommit :: Millis -> Subscription-autoCommit (Millis ms) = Subscription [] $+autoCommit (Millis ms) = Subscription (Set.empty) $ M.fromList [ ("enable.auto.commit", "true")- , ("auto.commit.interval.ms", show ms)+ , ("auto.commit.interval.ms", Text.pack $ show ms) ] -extraSubscriptionProps :: Map String String -> Subscription-extraSubscriptionProps = Subscription []+extraSubscriptionProps :: Map Text Text -> Subscription+extraSubscriptionProps = Subscription (Set.empty)
src/Kafka/Consumer/Types.hs view
@@ -1,15 +1,42 @@ {-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE DeriveGeneric #-} module Kafka.Consumer.Types+( KafkaConsumer(..)+, ConsumerGroupId(..)+, Offset(..)+, OffsetReset(..)+, RebalanceEvent(..)+, PartitionOffset(..)+, SubscribedPartitions(..)+, Timestamp(..)+, OffsetCommit(..)+, OffsetStoreSync(..)+, OffsetStoreMethod(..)+, TopicPartition(..)+, ConsumerRecord(..)+, crMapKey+, crMapValue+, crMapKV+-- why are these here? +-- * Deprecated+, sequenceFirst+, traverseFirst+, traverseFirstM+, traverseM+, bitraverseM+) where -import Data.Bifoldable-import Data.Bifunctor-import Data.Bitraversable-import Data.Int-import Data.Typeable-import Kafka.Internal.Setup-import Kafka.Types+import Data.Bifoldable (Bifoldable (..))+import Data.Bifunctor (Bifunctor (..))+import Data.Bitraversable (Bitraversable (..), bimapM, bisequenceA)+import Data.Int (Int64)+import Data.Text (Text)+import Data.Typeable (Typeable)+import GHC.Generics (Generic)+import Kafka.Internal.Setup (HasKafka (..), HasKafkaConf (..), Kafka (..), KafkaConf (..))+import Kafka.Types (Millis (..), PartitionId (..), TopicName (..)) data KafkaConsumer = KafkaConsumer { kcKafkaPtr :: !Kafka@@ -24,9 +51,9 @@ getKafkaConf = kcKafkaConf {-# INLINE getKafkaConf #-} -newtype ConsumerGroupId = ConsumerGroupId { unConsumerGroupId :: String} deriving (Show, Ord, Eq)-newtype Offset = Offset { unOffset :: Int64 } deriving (Show, Eq, Ord, Read)-data OffsetReset = Earliest | Latest deriving (Show, Eq)+newtype ConsumerGroupId = ConsumerGroupId { unConsumerGroupId :: Text } deriving (Show, Ord, Eq, Generic)+newtype Offset = Offset { unOffset :: Int64 } deriving (Show, Eq, Ord, Read, Generic)+data OffsetReset = Earliest | Latest deriving (Show, Eq, Generic) -- | A set of events which happen during the rebalancing process data RebalanceEvent =@@ -38,7 +65,7 @@ | RebalanceBeforeRevoke [(TopicName, PartitionId)] -- | Happens after the rejection is confirmed | RebalanceRevoke [(TopicName, PartitionId)]- deriving (Eq, Show)+ deriving (Eq, Show, Generic) data PartitionOffset = PartitionOffsetBeginning@@ -46,24 +73,24 @@ | PartitionOffset Int64 | PartitionOffsetStored | PartitionOffsetInvalid- deriving (Eq, Show)+ deriving (Eq, Show, Generic) data SubscribedPartitions = SubscribedPartitions [PartitionId] | SubscribedPartitionsAll- deriving (Show, Eq)+ deriving (Show, Eq, Generic) data Timestamp = CreateTime !Millis | LogAppendTime !Millis | NoTimestamp- deriving (Show, Eq, Read)+ deriving (Show, Eq, Read, Generic) -- | Offsets commit mode data OffsetCommit = OffsetCommit -- ^ Forces consumer to block until the broker offsets commit is done | OffsetCommitAsync -- ^ Offsets will be committed in a non-blocking way- deriving (Show, Eq)+ deriving (Show, Eq, Generic) -- | Indicates how offsets are to be synced to disk@@ -71,17 +98,20 @@ OffsetSyncDisable -- ^ Do not sync offsets (in Kafka: -1) | OffsetSyncImmediate -- ^ Sync immediately after each offset commit (in Kafka: 0) | OffsetSyncInterval Int -- ^ Sync after specified interval in millis+ deriving (Show, Eq, Generic) -- | Indicates the method of storing the offsets data OffsetStoreMethod = OffsetStoreBroker -- ^ Offsets are stored in Kafka broker (preferred) | OffsetStoreFile FilePath OffsetStoreSync -- ^ Offsets are stored in a file (and synced to disk according to the sync policy)+ deriving (Show, Eq, Generic) -- | Kafka topic partition structure data TopicPartition = TopicPartition { tpTopicName :: TopicName , tpPartition :: PartitionId- , tpOffset :: PartitionOffset } deriving (Show, Eq)+ , tpOffset :: PartitionOffset+ } deriving (Show, Eq, Generic) -- | Represents a /received/ message from Kafka (i.e. used in a consumer) data ConsumerRecord k v = ConsumerRecord@@ -92,7 +122,7 @@ , crKey :: !k , crValue :: !v }- deriving (Eq, Show, Read, Typeable)+ deriving (Eq, Show, Read, Typeable, Generic) instance Bifunctor ConsumerRecord where bimap f g (ConsumerRecord t p o ts k v) = ConsumerRecord t p o ts (f k) (g v)@@ -130,10 +160,12 @@ crMapKV = bimap {-# INLINE crMapKV #-} +{-# DEPRECATED sequenceFirst "Isn't concern of this library. Use 'bitraverse id pure'" #-} sequenceFirst :: (Bitraversable t, Applicative f) => t (f k) v -> f (t k v) sequenceFirst = bitraverse id pure {-# INLINE sequenceFirst #-} +{-# DEPRECATED traverseFirst "Isn't concern of this library. Use 'bitraverse f pure'" #-} traverseFirst :: (Bitraversable t, Applicative f) => (k -> f k') -> t k v@@ -141,6 +173,7 @@ traverseFirst f = bitraverse f pure {-# INLINE traverseFirst #-} +{-# DEPRECATED traverseFirstM "Isn't concern of this library. Use 'bitraverse id pure <$> bitraverse f pure r'" #-} traverseFirstM :: (Bitraversable t, Applicative f, Monad m) => (k -> m (f k')) -> t k v@@ -148,6 +181,7 @@ traverseFirstM f r = bitraverse id pure <$> bitraverse f pure r {-# INLINE traverseFirstM #-} +{-# DEPRECATED traverseM "Isn't concern of this library. Use 'sequenceA <$> traverse f r'" #-} traverseM :: (Traversable t, Applicative f, Monad m) => (v -> m (f v')) -> t v@@ -155,6 +189,7 @@ traverseM f r = sequenceA <$> traverse f r {-# INLINE traverseM #-} +{-# DEPRECATED bitraverseM "Isn't concern of this library. Use 'bisequenceA <$> bimapM f g r'" #-} bitraverseM :: (Bitraversable t, Applicative f, Monad m) => (k -> m (f k')) -> (v -> m (f v'))
src/Kafka/Dump.hs view
@@ -6,17 +6,33 @@ ) where --- import Kafka.Internal.RdKafka+ ( CSizePtr+ , rdKafkaConfDumpFree+ , peekCText+ , rdKafkaConfDump+ , rdKafkaTopicConfDump+ , rdKafkaDump+ , handleToCFile+ , rdKafkaConfPropertiesShow+ ) import Kafka.Internal.Setup+ ( HasKafka(..)+ , HasTopicConf(..)+ , HasKafkaConf(..)+ , getRdKafka+ , getRdTopicConf+ , getRdKafkaConf+ ) -import Control.Monad-import Control.Monad.IO.Class+import Control.Monad ((<=<))+import Control.Monad.IO.Class (MonadIO(liftIO)) import Data.Map.Strict (Map) import qualified Data.Map.Strict as Map-import Foreign-import Foreign.C.String-import System.IO+import Foreign (Ptr, alloca, Storable(peek, peekElemOff))+import Foreign.C.String (CString)+import System.IO (Handle)+import Data.Text (Text) -- | Prints out all supported Kafka conf properties to a handle hPrintSupportedKafkaConf :: MonadIO m => Handle -> m ()@@ -27,25 +43,25 @@ hPrintKafka h k = liftIO $ handleToCFile h "w" >>= \f -> rdKafkaDump f (getRdKafka k) -- | Returns a map of the current topic configuration-dumpTopicConf :: (MonadIO m, HasTopicConf t) => t -> m (Map String String)+dumpTopicConf :: (MonadIO m, HasTopicConf t) => t -> m (Map Text Text) dumpTopicConf t = liftIO $ parseDump (rdKafkaTopicConfDump (getRdTopicConf t)) -- | Returns a map of the current kafka configuration-dumpKafkaConf :: (MonadIO m, HasKafkaConf k) => k -> m (Map String String)+dumpKafkaConf :: (MonadIO m, HasKafkaConf k) => k -> m (Map Text Text) dumpKafkaConf k = liftIO $ parseDump (rdKafkaConfDump (getRdKafkaConf k)) -parseDump :: (CSizePtr -> IO (Ptr CString)) -> IO (Map String String)+parseDump :: (CSizePtr -> IO (Ptr CString)) -> IO (Map Text Text) parseDump cstr = alloca $ \sizeptr -> do strPtr <- cstr sizeptr size <- peek sizeptr - keysAndValues <- mapM (peekCString <=< peekElemOff strPtr) [0..(fromIntegral size - 1)]+ keysAndValues <- mapM (peekCText <=< peekElemOff strPtr) [0..(fromIntegral size - 1)] let ret = Map.fromList $ listToTuple keysAndValues rdKafkaConfDumpFree strPtr size return ret -listToTuple :: [String] -> [(String, String)]-listToTuple [] = []-listToTuple (k:v:t) = (k, v) : listToTuple t-listToTuple _ = error "list to tuple can only be called on even length lists"+listToTuple :: [Text] -> [(Text, Text)]+listToTuple [] = []+listToTuple (k:v:ts) = (k, v) : listToTuple ts+listToTuple _ = error "list to tuple can only be called on even length lists"
src/Kafka/Internal/CancellationToken.hs view
@@ -1,7 +1,13 @@ module Kafka.Internal.CancellationToken+( CancellationStatus(..)+, CancellationToken(..)+, newCancellationToken+, status+, cancel+) where -import Data.IORef+import Data.IORef (IORef, newIORef, readIORef, atomicWriteIORef) data CancellationStatus = Cancelled | Running deriving (Show, Eq) newtype CancellationToken = CancellationToken (IORef CancellationStatus)
src/Kafka/Internal/RdKafka.chs view
@@ -3,15 +3,22 @@ module Kafka.Internal.RdKafka where -import Control.Monad-import Data.Word-import Foreign-import Foreign.C.Error-import Foreign.C.String-import Foreign.C.Types-import System.IO-import System.Posix.IO-import System.Posix.Types+import Data.Text (Text)+import qualified Data.Text as Text+import Control.Monad (liftM)+import Data.Int (Int32, Int64)+import Data.Word (Word8)+import Foreign.Marshal.Alloc (alloca, allocaBytes)+import Foreign.Marshal.Array (peekArray, allocaArray)+import Foreign.Storable (Storable(..))+import Foreign.Ptr (Ptr, FunPtr, castPtr, nullPtr)+import Foreign.ForeignPtr (FinalizerPtr, addForeignPtrFinalizer, withForeignPtr, newForeignPtr, newForeignPtr_)+import Foreign.C.Error (Errno(..), getErrno)+import Foreign.C.String (CString, newCString, withCAString, peekCAString, peekCAStringLen, peekCString)+import Foreign.C.Types (CFile, CInt(..), CSize, CChar)+import System.IO (Handle, stdin, stdout, stderr)+import System.Posix.IO (handleToFd)+import System.Posix.Types (Fd(..)) #include <librdkafka/rdkafka.h> @@ -55,8 +62,13 @@ {#fun pure rd_kafka_errno2err as ^ {`Int'} -> `RdKafkaRespErrT' cIntToEnum #} +peekCAText :: CString -> IO Text+peekCAText cp = Text.pack <$> peekCAString cp -kafkaErrnoString :: IO (String)+peekCText :: CString -> IO Text+peekCText cp = Text.pack <$> peekCString cp++kafkaErrnoString :: IO String kafkaErrnoString = do (Errno num) <- getErrno return $ rdKafkaErr2str $ rdKafkaErrno2err (fromIntegral num)@@ -818,13 +830,13 @@ foreign import ccall unsafe "rdkafka.h &rd_kafka_destroy" rdKafkaDestroy :: FunPtr (Ptr RdKafkaT -> IO ()) -newRdKafkaT :: RdKafkaTypeT -> RdKafkaConfTPtr -> IO (Either String RdKafkaTPtr)+newRdKafkaT :: RdKafkaTypeT -> RdKafkaConfTPtr -> IO (Either Text RdKafkaTPtr) newRdKafkaT kafkaType confPtr = allocaBytes nErrorBytes $ \charPtr -> do duper <- rdKafkaConfDup confPtr ret <- rdKafkaNew kafkaType duper charPtr (fromIntegral nErrorBytes) withForeignPtr ret $ \realPtr -> do- if realPtr == nullPtr then peekCString charPtr >>= return . Left+ if realPtr == nullPtr then peekCText charPtr >>= return . Left else do addForeignPtrFinalizer rdKafkaDestroy ret return $ Right ret
src/Kafka/Internal/Setup.hs view
@@ -1,20 +1,46 @@-module Kafka.Internal.Setup where+module Kafka.Internal.Setup+( KafkaProps(..)+, TopicProps(..)+, Kafka(..)+, KafkaConf(..)+, TopicConf(..)+, HasKafka(..)+, HasKafkaConf(..)+, HasTopicConf(..)+, getRdKafka+, getRdKafkaConf+, getRdMsgQueue+, getRdTopicConf+, newTopicConf+, newKafkaConf+, kafkaConf+, topicConf+, checkConfSetValue+, setKafkaConfValue+, setAllKafkaConfValues+, setTopicConfValue+, setAllTopicConfValues+)+where -import Kafka.Internal.RdKafka-import Kafka.Types+import Kafka.Internal.RdKafka (RdKafkaConfResT(..), CCharBufPointer, RdKafkaQueueTPtr, RdKafkaTPtr, RdKafkaConfTPtr, RdKafkaTopicConfTPtr, nErrorBytes, rdKafkaTopicConfSet, newRdKafkaTopicConfT, newRdKafkaConfT, rdKafkaConfSet)+import Kafka.Types (KafkaError(..)) -import Control.Exception-import Control.Monad-import Data.IORef-import Foreign-import Foreign.C.String-import Kafka.Internal.CancellationToken+import Control.Exception (throw)+import Data.IORef (IORef, newIORef, readIORef)+import Foreign.Marshal.Alloc (allocaBytes)+import Foreign.C.String (peekCString)+import Kafka.Internal.CancellationToken (CancellationToken(..), newCancellationToken)+import qualified Data.Text as Text+import Data.Map (Map)+import Data.Text (Text)+import qualified Data.Map as Map -- -- Configuration ---newtype KafkaProps = KafkaProps [(String, String)] deriving (Show, Eq)-newtype TopicProps = TopicProps [(String, String)] deriving (Show, Eq)+newtype KafkaProps = KafkaProps (Map Text Text) deriving (Show, Eq)+newtype TopicProps = TopicProps (Map Text Text) deriving (Show, Eq) newtype Kafka = Kafka RdKafkaTPtr deriving Show data KafkaConf = KafkaConf RdKafkaConfTPtr (IORef (Maybe RdKafkaQueueTPtr)) CancellationToken newtype TopicConf = TopicConf RdKafkaTopicConfTPtr deriving Show@@ -80,25 +106,25 @@ RdKafkaConfOk -> return () RdKafkaConfInvalid -> do str <- peekCString charPtr- throw $ KafkaInvalidConfigurationValue str+ throw $ KafkaInvalidConfigurationValue (Text.pack str) RdKafkaConfUnknown -> do str <- peekCString charPtr- throw $ KafkaUnknownConfigurationKey str+ throw $ KafkaUnknownConfigurationKey (Text.pack str) -setKafkaConfValue :: KafkaConf -> String -> String -> IO ()+setKafkaConfValue :: KafkaConf -> Text -> Text -> IO () setKafkaConfValue (KafkaConf confPtr _ _) key value = allocaBytes nErrorBytes $ \charPtr -> do- err <- rdKafkaConfSet confPtr key value charPtr (fromIntegral nErrorBytes)+ err <- rdKafkaConfSet confPtr (Text.unpack key) (Text.unpack value) charPtr (fromIntegral nErrorBytes) checkConfSetValue err charPtr setAllKafkaConfValues :: KafkaConf -> KafkaProps -> IO ()-setAllKafkaConfValues conf (KafkaProps props) = forM_ props $ uncurry (setKafkaConfValue conf)+setAllKafkaConfValues conf (KafkaProps props) = Map.foldMapWithKey (setKafkaConfValue conf) props --forM_ props $ uncurry (setKafkaConfValue conf) -setTopicConfValue :: TopicConf -> String -> String -> IO ()+setTopicConfValue :: TopicConf -> Text -> Text -> IO () setTopicConfValue (TopicConf confPtr) key value = allocaBytes nErrorBytes $ \charPtr -> do- err <- rdKafkaTopicConfSet confPtr key value charPtr (fromIntegral nErrorBytes)+ err <- rdKafkaTopicConfSet confPtr (Text.unpack key) (Text.unpack value) charPtr (fromIntegral nErrorBytes) checkConfSetValue err charPtr setAllTopicConfValues :: TopicConf -> TopicProps -> IO ()-setAllTopicConfValues conf (TopicProps props) = forM_ props $ uncurry (setTopicConfValue conf)+setAllTopicConfValues conf (TopicProps props) = Map.foldMapWithKey (setTopicConfValue conf) props --forM_ props $ uncurry (setTopicConfValue conf)
@@ -1,18 +1,40 @@ module Kafka.Internal.Shared+( runEventLoop+, pollEvents+, word8PtrToBS+, kafkaRespErr+, throwOnError+, hasError+, rdKafkaErrorToEither+, kafkaErrorToEither+, kafkaErrorToMaybe+, maybeToLeft+, readPayload+, readTopic+, readKey+, readTimestamp+, readBS+) where +import Data.Text (Text)+import qualified Data.Text as Text import Control.Concurrent (forkIO, rtsSupportsBoundThreads)-import Control.Exception+import Control.Exception (throw) import Control.Monad (void, when) import qualified Data.ByteString as BS import qualified Data.ByteString.Internal as BSI-import Foreign hiding (void)-import Foreign.C.Error-import Kafka.Consumer.Types+import Data.Word (Word8)+import Foreign.Ptr (Ptr, nullPtr)+import Foreign.Marshal.Alloc (alloca)+import Foreign.ForeignPtr (newForeignPtr_)+import Foreign.Storable (Storable(peek))+import Foreign.C.Error (Errno(..))+import Kafka.Consumer.Types (Timestamp(..)) import Kafka.Internal.CancellationToken as CToken-import Kafka.Internal.RdKafka-import Kafka.Internal.Setup-import Kafka.Types+import Kafka.Internal.RdKafka (RdKafkaTimestampTypeT(..), RdKafkaMessageTPtr, RdKafkaMessageT(..), RdKafkaRespErrT(..), Word8Ptr, rdKafkaPoll, rdKafkaErrno2err, rdKafkaTopicName, rdKafkaMessageTimestamp)+import Kafka.Internal.Setup (HasKafka(..), Kafka(..))+import Kafka.Types (KafkaError(..), Timeout(..), Millis(..)) runEventLoop :: HasKafka a => a -> CancellationToken -> Maybe Timeout -> IO () runEventLoop k ct timeout =@@ -38,7 +60,7 @@ kafkaRespErr (Errno num) = KafkaResponseError $ rdKafkaErrno2err (fromIntegral num) {-# INLINE kafkaRespErr #-} -throwOnError :: IO (Maybe String) -> IO ()+throwOnError :: IO (Maybe Text) -> IO () throwOnError action = do m <- action case m of@@ -76,8 +98,8 @@ readPayload :: RdKafkaMessageT -> IO (Maybe BS.ByteString) readPayload = readBS len'RdKafkaMessageT payload'RdKafkaMessageT -readTopic :: RdKafkaMessageT -> IO String-readTopic msg = newForeignPtr_ (topic'RdKafkaMessageT msg) >>= rdKafkaTopicName+readTopic :: RdKafkaMessageT -> IO Text+readTopic msg = newForeignPtr_ (topic'RdKafkaMessageT msg) >>= (fmap Text.pack . rdKafkaTopicName) readKey :: RdKafkaMessageT -> IO (Maybe BSI.ByteString) readKey = readBS keyLen'RdKafkaMessageT key'RdKafkaMessageT
src/Kafka/Metadata.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE OverloadedStrings #-} module Kafka.Metadata ( KafkaMetadata(..), BrokerMetadata(..), TopicMetadata(..), PartitionMetadata(..) , WatermarkOffsets(..)@@ -12,33 +14,36 @@ ) where -import Control.Arrow (left)-import Control.Exception (bracket)-import Control.Monad.IO.Class (MonadIO, liftIO)-import Data.Bifunctor-import Data.ByteString (ByteString, pack)-import Data.Monoid ((<>))-import qualified Data.Set as S-import Foreign-import Foreign.C.String-import Kafka.Consumer.Convert-import Kafka.Consumer.Types-import Kafka.Internal.RdKafka-import Kafka.Internal.Setup-import Kafka.Internal.Shared-import Kafka.Types+import Control.Arrow (left)+import Control.Exception (bracket)+import Control.Monad.IO.Class (MonadIO (liftIO))+import Data.Bifunctor (bimap)+import Data.ByteString (ByteString, pack)+import Data.Monoid ((<>))+import Data.Text (Text)+import Foreign (Storable (peek), peekArray, withForeignPtr)+import GHC.Generics (Generic)+import Kafka.Consumer.Convert (fromNativeTopicPartitionList'', toNativeTopicPartitionList)+import Kafka.Consumer.Types (ConsumerGroupId (..), Offset (..), PartitionOffset (..), TopicPartition (..))+import Kafka.Internal.RdKafka (RdKafkaGroupInfoT (..), RdKafkaGroupListT (..), RdKafkaGroupListTPtr, RdKafkaGroupMemberInfoT (..), RdKafkaMetadataBrokerT (..), RdKafkaMetadataPartitionT (..), RdKafkaMetadataT (..), RdKafkaMetadataTPtr, RdKafkaMetadataTopicT (..), RdKafkaRespErrT (..), RdKafkaTPtr, destroyUnmanagedRdKafkaTopic, newUnmanagedRdKafkaTopicT, peekCAText, rdKafkaListGroups, rdKafkaMetadata, rdKafkaOffsetsForTimes, rdKafkaQueryWatermarkOffsets)+import Kafka.Internal.Setup (HasKafka (..), Kafka (..))+import Kafka.Internal.Shared (kafkaErrorToMaybe)+import Kafka.Types (BrokerId (..), ClientId (..), KafkaError (..), Millis (..), PartitionId (..), Timeout (..), TopicName (..)) +import qualified Data.Set as S+import qualified Data.Text as Text+ data KafkaMetadata = KafkaMetadata { kmBrokers :: [BrokerMetadata] , kmTopics :: [TopicMetadata] , kmOrigBroker :: !BrokerId- } deriving (Show, Eq)+ } deriving (Show, Eq, Generic) data BrokerMetadata = BrokerMetadata { bmBrokerId :: !BrokerId- , bmBrokerHost :: !String+ , bmBrokerHost :: !Text , bmBrokerPort :: !Int- } deriving (Show, Eq)+ } deriving (Show, Eq, Generic) data PartitionMetadata = PartitionMetadata { pmPartitionId :: !PartitionId@@ -46,39 +51,39 @@ , pmLeader :: !BrokerId , pmReplicas :: [BrokerId] , pmInSyncReplicas :: [BrokerId]- } deriving (Show, Eq)+ } deriving (Show, Eq, Generic) data TopicMetadata = TopicMetadata { tmTopicName :: !TopicName , tmPartitions :: [PartitionMetadata] , tmError :: Maybe KafkaError- } deriving (Show, Eq)+ } deriving (Show, Eq, Generic) data WatermarkOffsets = WatermarkOffsets { woTopicName :: !TopicName , woPartitionId :: !PartitionId , woLowWatermark :: !Offset , woHighWatermark :: !Offset- } deriving (Show, Eq)+ } deriving (Show, Eq, Generic) -newtype GroupMemberId = GroupMemberId String deriving (Show, Eq, Read, Ord)+newtype GroupMemberId = GroupMemberId Text deriving (Show, Eq, Read, Ord) data GroupMemberInfo = GroupMemberInfo { gmiMemberId :: !GroupMemberId , gmiClientId :: !ClientId- , gmiClientHost :: !String+ , gmiClientHost :: !Text , gmiMetadata :: !ByteString , gmiAssignment :: !ByteString- } deriving (Show, Eq)+ } deriving (Show, Eq, Generic) -newtype GroupProtocolType = GroupProtocolType String deriving (Show, Eq, Read, Ord)-newtype GroupProtocol = GroupProtocol String deriving (Show, Eq, Read, Ord)+newtype GroupProtocolType = GroupProtocolType Text deriving (Show, Eq, Read, Ord, Generic)+newtype GroupProtocol = GroupProtocol Text deriving (Show, Eq, Read, Ord, Generic) data GroupState = GroupPreparingRebalance -- ^ Group is preparing to rebalance | GroupEmpty -- ^ Group has no more members, but lingers until all offsets have expired | GroupAwaitingSync -- ^ Group is awaiting state assignment from the leader | GroupStable -- ^ Group is stable | GroupDead -- ^ Group has no more members and its metadata is being removed- deriving (Show, Eq, Read, Ord)+ deriving (Show, Eq, Read, Ord, Generic) data GroupInfo = GroupInfo { giGroup :: !ConsumerGroupId@@ -87,7 +92,7 @@ , giProtocolType :: !GroupProtocolType , giProtocol :: !GroupProtocol , giMembers :: [GroupMemberInfo]- } deriving (Show, Eq)+ } deriving (Show, Eq, Generic) -- | Returns metadata for all topics in the cluster allTopicsMetadata :: (MonadIO m, HasKafka k) => k -> Timeout -> m (Either KafkaError KafkaMetadata)@@ -99,12 +104,12 @@ topicMetadata :: (MonadIO m, HasKafka k) => k -> Timeout -> TopicName -> m (Either KafkaError KafkaMetadata) topicMetadata k (Timeout timeout) (TopicName tn) = liftIO $ bracket mkTopic clTopic $ \mbt -> case mbt of- Left err -> return (Left $ KafkaError err)+ Left err -> return (Left $ KafkaError (Text.pack err)) Right t -> do meta <- rdKafkaMetadata (getKafkaPtr k) False (Just t) timeout traverse fromKafkaMetadataPtr (left KafkaResponseError meta) where- mkTopic = newUnmanagedRdKafkaTopicT (getKafkaPtr k) tn Nothing+ mkTopic = newUnmanagedRdKafkaTopicT (getKafkaPtr k) (Text.unpack tn) Nothing clTopic = either (return . const ()) destroyUnmanagedRdKafkaTopic -- | Query broker for low (oldest/beginning) and high (newest/end) offsets for a given topic.@@ -126,7 +131,7 @@ -- | Query broker for low (oldest/beginning) and high (newest/end) offsets for a specific partition partitionWatermarkOffsets :: (MonadIO m, HasKafka k) => k -> Timeout -> TopicName -> PartitionId -> m (Either KafkaError WatermarkOffsets) partitionWatermarkOffsets k (Timeout timeout) (TopicName t) (PartitionId p) = liftIO $ do- offs <- rdKafkaQueryWatermarkOffsets (getKafkaPtr k) t p timeout+ offs <- rdKafkaQueryWatermarkOffsets (getKafkaPtr k) (Text.unpack t) p timeout return $ bimap KafkaResponseError toWatermark offs where toWatermark (l, h) = WatermarkOffsets (TopicName t) (PartitionId p) (Offset l) (Offset h)@@ -181,7 +186,7 @@ -- | Describe a given consumer group. consumerGroupInfo :: (MonadIO m, HasKafka k) => k -> Timeout -> ConsumerGroupId -> m (Either KafkaError [GroupInfo]) consumerGroupInfo k (Timeout timeout) (ConsumerGroupId gn) = liftIO $ do- res <- rdKafkaListGroups (getKafkaPtr k) (Just gn) timeout+ res <- rdKafkaListGroups (getKafkaPtr k) (Just (Text.unpack gn)) timeout traverse fromGroupInfoListPtr (left KafkaResponseError res) -------------------------------------------------------------------------------@@ -201,10 +206,10 @@ fromGroupInfoPtr gi = do --bmd <- peek (broker'RdKafkaGroupInfoT gi) -- >>= fromBrokerMetadataPtr --xxx <- fromBrokerMetadataPtr bmd- cid <- peekCAString $ group'RdKafkaGroupInfoT gi- stt <- peekCAString $ state'RdKafkaGroupInfoT gi- prt <- peekCAString $ protocolType'RdKafkaGroupInfoT gi- pr <- peekCAString $ protocol'RdKafkaGroupInfoT gi+ cid <- peekCAText $ group'RdKafkaGroupInfoT gi+ stt <- peekCAText $ state'RdKafkaGroupInfoT gi+ prt <- peekCAText $ protocolType'RdKafkaGroupInfoT gi+ pr <- peekCAText $ protocol'RdKafkaGroupInfoT gi mbs <- peekArray (memberCnt'RdKafkaGroupInfoT gi) (members'RdKafkaGroupInfoT gi) mbl <- mapM fromGroupMemberInfoPtr mbs return GroupInfo@@ -219,9 +224,9 @@ fromGroupMemberInfoPtr :: RdKafkaGroupMemberInfoT -> IO GroupMemberInfo fromGroupMemberInfoPtr mi = do- mid <- peekCAString $ memberId'RdKafkaGroupMemberInfoT mi- cid <- peekCAString $ clientId'RdKafkaGroupMemberInfoT mi- hst <- peekCAString $ clientHost'RdKafkaGroupMemberInfoT mi+ mid <- peekCAText $ memberId'RdKafkaGroupMemberInfoT mi+ cid <- peekCAText $ clientId'RdKafkaGroupMemberInfoT mi+ hst <- peekCAText $ clientHost'RdKafkaGroupMemberInfoT mi mtd <- peekArray (memberMetadataSize'RdKafkaGroupMemberInfoT mi) (memberMetadata'RdKafkaGroupMemberInfoT mi) ass <- peekArray (memberAssignmentSize'RdKafkaGroupMemberInfoT mi) (memberAssignment'RdKafkaGroupMemberInfoT mi) return GroupMemberInfo@@ -234,7 +239,7 @@ fromTopicMetadataPtr :: RdKafkaMetadataTopicT -> IO TopicMetadata fromTopicMetadataPtr tm = do- tnm <- peekCAString (topic'RdKafkaMetadataTopicT tm)+ tnm <- peekCAText (topic'RdKafkaMetadataTopicT tm) pts <- peekArray (partitionCnt'RdKafkaMetadataTopicT tm) (partitions'RdKafkaMetadataTopicT tm) pms <- mapM fromPartitionMetadataPtr pts return TopicMetadata@@ -258,7 +263,7 @@ fromBrokerMetadataPtr :: RdKafkaMetadataBrokerT -> IO BrokerMetadata fromBrokerMetadataPtr bm = do- host <- peekCAString (host'RdKafkaMetadataBrokerT bm)+ host <- peekCAText (host'RdKafkaMetadataBrokerT bm) return BrokerMetadata { bmBrokerId = BrokerId (id'RdKafkaMetadataBrokerT bm) , bmBrokerHost = host@@ -280,11 +285,11 @@ , kmOrigBroker = BrokerId $ fromIntegral (origBrokerId'RdKafkaMetadataT km) } -groupStateFromKafkaString :: String -> GroupState+groupStateFromKafkaString :: Text -> GroupState groupStateFromKafkaString s = case s of "PreparingRebalance" -> GroupPreparingRebalance "AwaitingSync" -> GroupAwaitingSync "Stable" -> GroupStable "Dead" -> GroupDead "Empty" -> GroupEmpty- _ -> error $ "Unknown group state: " <> s+ _ -> error $ "Unknown group state: " <> (Text.unpack s)
src/Kafka/Producer.hs view
@@ -12,23 +12,36 @@ where import Control.Arrow ((&&&))-import Control.Exception-import Control.Monad-import Control.Monad.IO.Class+import Control.Exception (bracket)+import Control.Monad (forM, forM_, (<=<))+import Control.Monad.IO.Class (MonadIO(liftIO)) import qualified Data.ByteString as BS import qualified Data.ByteString.Internal as BSI import Data.Function (on) import Data.List (groupBy, sortBy)-import qualified Data.Map as M import Data.Ord (comparing)-import Foreign hiding (void)+import qualified Data.Text as Text+import Foreign.ForeignPtr (withForeignPtr, newForeignPtr_)+import Foreign.Marshal.Array (withArrayLen)+import Foreign.Ptr (Ptr, nullPtr, plusPtr)+import Foreign.Storable (Storable(..)) import Kafka.Internal.CancellationToken as CToken import Kafka.Internal.RdKafka-import Kafka.Internal.Setup-import Kafka.Internal.Shared-import Kafka.Producer.Callbacks-import Kafka.Producer.Convert-import Kafka.Producer.Types+ ( RdKafkaMessageT(..)+ , RdKafkaTypeT(..)+ , RdKafkaRespErrT(..)+ , newRdKafkaT+ , newUnmanagedRdKafkaTopicT+ , destroyUnmanagedRdKafkaTopic+ , rdKafkaProduce+ , rdKafkaSetLogLevel+ , rdKafkaProduceBatch+ , rdKafkaOutqLen+ )+import Kafka.Internal.Setup (KafkaConf(..), Kafka(..), TopicConf(..), kafkaConf, KafkaProps(..), topicConf, TopicProps(..))+import Kafka.Internal.Shared (pollEvents)+import Kafka.Producer.Convert (copyMsgFlags, producePartitionCInt, producePartitionInt, handleProduceErr)+import Kafka.Producer.Types (KafkaProducer(..)) import Kafka.Producer.ProducerProperties as X import Kafka.Producer.Types as X hiding (KafkaProducer)@@ -56,8 +69,8 @@ -- A newly created producer must be closed with 'closeProducer' function. newProducer :: MonadIO m => ProducerProperties -> m (Either KafkaError KafkaProducer) newProducer pps = liftIO $ do- kc@(KafkaConf kc' _ _) <- kafkaConf (KafkaProps $ M.toList (ppKafkaProps pps))- tc <- topicConf (TopicProps $ M.toList (ppTopicProps pps))+ kc@(KafkaConf kc' _ _) <- kafkaConf (KafkaProps $ (ppKafkaProps pps))+ tc <- topicConf (TopicProps $ (ppTopicProps pps)) -- set callbacks forM_ (ppCallbacks pps) (\setCb -> setCb kc)@@ -81,11 +94,11 @@ pollEvents kp (Just $ Timeout 0) -- fire callbacks if any exist (handle delivery reports) bracket (mkTopic $ prTopic m) clTopic withTopic where- mkTopic (TopicName tn) = newUnmanagedRdKafkaTopicT k tn (Just tc)+ mkTopic (TopicName tn) = newUnmanagedRdKafkaTopicT k (Text.unpack tn) (Just tc) clTopic = either (return . const ()) destroyUnmanagedRdKafkaTopic - withTopic (Left err) = return . Just . KafkaError $ err+ withTopic (Left err) = return . Just . KafkaError $ Text.pack err withTopic (Right t) = withBS (prValue m) $ \payloadPtr payloadLength -> withBS (prKey m) $ \keyPtr keyLength ->@@ -112,14 +125,14 @@ mkSortKey = prTopic &&& prPartition mkBatches = groupBy ((==) `on` mkSortKey) . sortBy (comparing mkSortKey) - mkTopic (TopicName tn) = newUnmanagedRdKafkaTopicT k tn (Just tc)+ mkTopic (TopicName tn) = newUnmanagedRdKafkaTopicT k (Text.unpack tn) (Just tc) clTopic = either (return . const ()) destroyUnmanagedRdKafkaTopic sendBatch [] = return [] sendBatch batch = bracket (mkTopic $ prTopic (head batch)) clTopic (withTopic batch) - withTopic ms (Left err) = return $ (, KafkaError err) <$> ms+ withTopic ms (Left err) = return $ (, KafkaError (Text.pack err)) <$> ms withTopic ms (Right t) = do let (partInt, partCInt) = (producePartitionInt &&& producePartitionCInt) $ prPartition (head ms) withForeignPtr t $ \topicPtr -> do
src/Kafka/Producer/Callbacks.hs view
@@ -4,15 +4,16 @@ ) where -import Foreign-import Foreign.C.Error+import Foreign.C.Error (getErrno)+import Foreign.Ptr (Ptr, nullPtr)+import Foreign.Storable (Storable(peek)) import Kafka.Callbacks as X-import Kafka.Consumer.Types-import Kafka.Internal.RdKafka-import Kafka.Internal.Setup-import Kafka.Internal.Shared-import Kafka.Producer.Types-import Kafka.Types+import Kafka.Consumer.Types (Offset(..))+import Kafka.Internal.RdKafka (RdKafkaMessageT(..), RdKafkaRespErrT(..), rdKafkaConfSetDrMsgCb)+import Kafka.Internal.Setup (KafkaConf(..), getRdKafkaConf)+import Kafka.Internal.Shared (kafkaRespErr, readTopic, readKey, readPayload)+import Kafka.Producer.Types (ProducerRecord(..), DeliveryReport(..), ProducePartition(..))+import Kafka.Types (KafkaError(..), TopicName(..)) -- | Sets the callback for delivery reports. deliveryCallback :: (DeliveryReport -> IO ()) -> KafkaConf -> IO ()
src/Kafka/Producer/Convert.hs view
@@ -1,12 +1,17 @@ module Kafka.Producer.Convert+( copyMsgFlags+, producePartitionInt+, producePartitionCInt+, handleProduceErr+) where -import Foreign.C.Error-import Foreign.C.Types-import Kafka.Internal.RdKafka-import Kafka.Internal.Shared-import Kafka.Types-import Kafka.Producer.Types+import Foreign.C.Error (getErrno)+import Foreign.C.Types (CInt)+import Kafka.Internal.RdKafka (rdKafkaMsgFlagCopy)+import Kafka.Internal.Shared (kafkaRespErr)+import Kafka.Types (KafkaError(..))+import Kafka.Producer.Types (ProducePartition(..)) copyMsgFlags :: Int copyMsgFlags = rdKafkaMsgFlagCopy
src/Kafka/Producer/ProducerProperties.hs view
@@ -1,22 +1,36 @@+{-# LANGUAGE OverloadedStrings #-}+ module Kafka.Producer.ProducerProperties-( module Kafka.Producer.ProducerProperties+( ProducerProperties(..)+, brokersList+, setCallback+, logLevel+, compression+, topicCompression+, sendTimeout+, extraProps+, suppressDisconnectLogs+, extraTopicProps+, debugOptions , module Kafka.Producer.Callbacks ) where -import Control.Monad-import qualified Data.List as L+import Data.Text (Text)+import qualified Data.Text as Text+import Control.Monad (MonadPlus(mplus)) import Data.Map (Map) import qualified Data.Map as M import Data.Semigroup as Sem-import Kafka.Internal.Setup+import Kafka.Internal.Setup (KafkaConf(..))+import Kafka.Types (KafkaDebug(..), Timeout(..), KafkaCompressionCodec(..), KafkaLogLevel(..), BrokerAddress(..), kafkaDebugToText, kafkaCompressionCodecToText) + import Kafka.Producer.Callbacks-import Kafka.Types -- | Properties to create 'KafkaProducer'. data ProducerProperties = ProducerProperties- { ppKafkaProps :: Map String String- , ppTopicProps :: Map String String+ { ppKafkaProps :: Map Text Text+ , ppTopicProps :: Map Text Text , ppLogLevel :: Maybe KafkaLogLevel , ppCallbacks :: [KafkaConf -> IO ()] }@@ -40,7 +54,7 @@ brokersList :: [BrokerAddress] -> ProducerProperties brokersList bs =- let bs' = L.intercalate "," ((\(BrokerAddress x) -> x) <$> bs)+ let bs' = Text.intercalate "," ((\(BrokerAddress x) -> x) <$> bs) in extraProps $ M.fromList [("bootstrap.servers", bs')] setCallback :: (KafkaConf -> IO ()) -> ProducerProperties@@ -53,19 +67,19 @@ compression :: KafkaCompressionCodec -> ProducerProperties compression c =- extraProps $ M.singleton "compression.codec" (kafkaCompressionCodecToString c)+ extraProps $ M.singleton "compression.codec" (kafkaCompressionCodecToText c) topicCompression :: KafkaCompressionCodec -> ProducerProperties topicCompression c =- extraTopicProps $ M.singleton "compression.codec" (kafkaCompressionCodecToString c)+ extraTopicProps $ M.singleton "compression.codec" (kafkaCompressionCodecToText c) sendTimeout :: Timeout -> ProducerProperties sendTimeout (Timeout t) =- extraTopicProps $ M.singleton "message.timeout.ms" (show t)+ extraTopicProps $ M.singleton "message.timeout.ms" (Text.pack $ show t) -- | Any configuration options that are supported by /librdkafka/. -- The full list can be found <https://github.com/edenhill/librdkafka/blob/master/CONFIGURATION.md here>-extraProps :: Map String String -> ProducerProperties+extraProps :: Map Text Text -> ProducerProperties extraProps m = mempty { ppKafkaProps = m } -- | Suppresses producer disconnects logs.@@ -78,7 +92,7 @@ -- | Any configuration options that are supported by /librdkafka/. -- The full list can be found <https://github.com/edenhill/librdkafka/blob/master/CONFIGURATION.md here>-extraTopicProps :: Map String String -> ProducerProperties+extraTopicProps :: Map Text Text -> ProducerProperties extraTopicProps m = mempty { ppTopicProps = m } -- | Sets debug features for the producer@@ -86,5 +100,5 @@ debugOptions :: [KafkaDebug] -> ProducerProperties debugOptions [] = extraProps M.empty debugOptions d =- let points = L.intercalate "," (kafkaDebugToString <$> d)+ let points = Text.intercalate "," (kafkaDebugToText <$> d) in extraProps $ M.fromList [("debug", points)]
src/Kafka/Producer/Types.hs view
@@ -1,13 +1,19 @@ {-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE DeriveGeneric #-} module Kafka.Producer.Types-+( KafkaProducer(..)+, ProducerRecord(..)+, ProducePartition(..)+, DeliveryReport(..)+) where -import qualified Data.ByteString as BS-import Data.Typeable-import Kafka.Internal.Setup-import Kafka.Types-import Kafka.Consumer.Types+import Data.ByteString+import Data.Typeable (Typeable)+import GHC.Generics (Generic)+import Kafka.Consumer.Types (Offset (..))+import Kafka.Internal.Setup (HasKafka (..), HasKafkaConf (..), HasTopicConf (..), Kafka (..), KafkaConf (..), TopicConf (..))+import Kafka.Types (KafkaError (..), TopicName (..)) -- | Main pointer to Kafka object, which contains our brokers data KafkaProducer = KafkaProducer@@ -32,33 +38,17 @@ data ProducerRecord = ProducerRecord { prTopic :: !TopicName , prPartition :: !ProducePartition- , prKey :: Maybe BS.ByteString- , prValue :: Maybe BS.ByteString- } deriving (Eq, Show, Typeable)+ , prKey :: Maybe ByteString+ , prValue :: Maybe ByteString+ } deriving (Eq, Show, Typeable, Generic) data ProducePartition = SpecifiedPartition {-# UNPACK #-} !Int -- the partition number of the topic | UnassignedPartition- deriving (Show, Eq, Ord, Typeable)+ deriving (Show, Eq, Ord, Typeable, Generic) data DeliveryReport = DeliverySuccess ProducerRecord Offset | DeliveryFailure ProducerRecord KafkaError | NoMessageError KafkaError- deriving (Show, Eq)---- -- | Represents the report of a successfully delivered message.--- data ProducerSuccess = ProducerSuccess--- { psTopic :: !TopicName -- ^ Kafka topic this message was received from--- , psPartition :: !PartitionId -- ^ Kafka partition this message was received from--- , psOffset :: !Offset -- ^ Offset within the 'crPartition' Kafka partition--- , psKey :: !(Maybe BS.ByteString)--- , psValue :: !(Maybe BS.ByteString)--- }--- deriving (Eq, Show, Read, Typeable)---- -- | Represents the failure to deliver a message.--- data ProducerError = ProducerError--- { peValue :: !(Maybe BS.ByteString) -- ^ The message that failed.--- , peError :: KafkaError -- ^ The reason for the failure.--- } deriving (Eq, Show)+ deriving (Show, Eq, Generic)
src/Kafka/Types.hs view
@@ -1,20 +1,46 @@ {-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE OverloadedStrings #-} module Kafka.Types+( BrokerId(..)+, PartitionId(..)+, Millis(..)+, ClientId(..)+, BatchSize(..)+, TopicName(..)+, BrokerAddress(..)+, Timeout(..)+, KafkaLogLevel(..)+, KafkaError(..)+, KafkaDebug(..)+, KafkaCompressionCodec(..)+, TopicType(..)+, topicType+, kafkaDebugToText+, kafkaCompressionCodecToText+) where -import Control.Exception-import Data.Int-import Data.Typeable-import Kafka.Internal.RdKafka+import Control.Exception (Exception (..))+import Data.Int (Int64)+import Data.Text (Text, isPrefixOf)+import Data.Typeable (Typeable)+import GHC.Generics (Generic)+import Kafka.Internal.RdKafka (RdKafkaRespErrT, rdKafkaErr2name, rdKafkaErr2str) -newtype BrokerId = BrokerId { unBrokerId :: Int} deriving (Show, Eq, Ord, Read)+newtype BrokerId = BrokerId { unBrokerId :: Int } deriving (Show, Eq, Ord, Read, Generic) -newtype PartitionId = PartitionId { unPartitionId :: Int} deriving (Show, Eq, Read, Ord, Enum)-newtype Millis = Millis { unMillis :: Int64 } deriving (Show, Read, Eq, Ord, Num)-newtype ClientId = ClientId { unClientId :: String} deriving (Show, Eq, Ord)-newtype BatchSize = BatchSize { unBatchSize :: Int } deriving (Show, Read, Eq, Ord, Num)+newtype PartitionId = PartitionId { unPartitionId :: Int } deriving (Show, Eq, Read, Ord, Enum, Generic)+newtype Millis = Millis { unMillis :: Int64 } deriving (Show, Read, Eq, Ord, Num, Generic)+newtype ClientId = ClientId { unClientId :: Text } deriving (Show, Eq, Ord, Generic)+newtype BatchSize = BatchSize { unBatchSize :: Int } deriving (Show, Read, Eq, Ord, Num, Generic) +data TopicType =+ User -- ^ Normal topics that are created by user.+ | System -- ^ Topics starting with "__" (__consumer_offsets, __confluent.support.metrics) are considered "system" topics+ deriving (Show, Read, Eq, Ord, Generic)+ -- | Topic name to be consumed -- -- Wildcard (regex) topics are supported by the /librdkafka/ assignor:@@ -22,52 +48,37 @@ -- be regex-matched to the full list of topics in the cluster and matching -- topics will be added to the subscription list. newtype TopicName =- TopicName { unTopicName :: String } -- ^ a simple topic name or a regex if started with @^@- deriving (Show, Eq, Ord, Read)+ TopicName { unTopicName :: Text } -- ^ a simple topic name or a regex if started with @^@+ deriving (Show, Eq, Ord, Read, Generic) +topicType :: TopicName -> TopicType+topicType (TopicName tn) =+ if "__" `isPrefixOf` tn then System else User+{-# INLINE topicType #-}+ -- | Kafka broker address string (e.g. @broker1:9092@)-newtype BrokerAddress = BrokerAddress { unBrokerAddress :: String } deriving (Show, Eq)+newtype BrokerAddress = BrokerAddress { unBrokerAddress :: Text } deriving (Show, Eq, Generic) -- | Timeout in milliseconds-newtype Timeout = Timeout { unTimeout :: Int } deriving (Show, Eq, Read)+newtype Timeout = Timeout { unTimeout :: Int } deriving (Show, Eq, Read, Generic) -- | Log levels for /librdkafka/. data KafkaLogLevel = KafkaLogEmerg | KafkaLogAlert | KafkaLogCrit | KafkaLogErr | KafkaLogWarning | KafkaLogNotice | KafkaLogInfo | KafkaLogDebug- deriving (Show, Eq)--instance Enum KafkaLogLevel where- toEnum 0 = KafkaLogEmerg- toEnum 1 = KafkaLogAlert- toEnum 2 = KafkaLogCrit- toEnum 3 = KafkaLogErr- toEnum 4 = KafkaLogWarning- toEnum 5 = KafkaLogNotice- toEnum 6 = KafkaLogInfo- toEnum 7 = KafkaLogDebug- toEnum _ = undefined-- fromEnum KafkaLogEmerg = 0- fromEnum KafkaLogAlert = 1- fromEnum KafkaLogCrit = 2- fromEnum KafkaLogErr = 3- fromEnum KafkaLogWarning = 4- fromEnum KafkaLogNotice = 5- fromEnum KafkaLogInfo = 6- fromEnum KafkaLogDebug = 7+ deriving (Show, Enum, Eq) -- -- | Any Kafka errors data KafkaError =- KafkaError String+ KafkaError Text | KafkaInvalidReturnValue- | KafkaBadSpecification String+ | KafkaBadSpecification Text | KafkaResponseError RdKafkaRespErrT- | KafkaInvalidConfigurationValue String- | KafkaUnknownConfigurationKey String+ | KafkaInvalidConfigurationValue Text+ | KafkaUnknownConfigurationKey Text | KafkaBadConfiguration- deriving (Eq, Show, Typeable)+ deriving (Eq, Show, Typeable, Generic) instance Exception KafkaError where displayException (KafkaResponseError err) =@@ -87,10 +98,10 @@ | DebugFetch | DebugFeature | DebugAll- deriving (Eq, Show, Typeable)+ deriving (Eq, Show, Typeable, Generic) -kafkaDebugToString :: KafkaDebug -> String-kafkaDebugToString d =case d of+kafkaDebugToText :: KafkaDebug -> Text+kafkaDebugToText d = case d of DebugGeneric -> "generic" DebugBroker -> "broker" DebugTopic -> "topic"@@ -109,10 +120,10 @@ | Gzip | Snappy | Lz4- deriving (Eq, Show, Typeable)+ deriving (Eq, Show, Typeable, Generic) -kafkaCompressionCodecToString :: KafkaCompressionCodec -> String-kafkaCompressionCodecToString c = case c of+kafkaCompressionCodecToText :: KafkaCompressionCodec -> Text+kafkaCompressionCodecToText c = case c of NoCompression -> "none" Gzip -> "gzip" Snappy -> "snappy"
tests-it/Kafka/IntegrationSpec.hs view
@@ -8,7 +8,7 @@ import Control.Monad.Loops import qualified Data.ByteString as BS import Data.Either-import Data.Map+import Data.Map (fromList) import Data.Monoid ((<>)) import Kafka.Consumer as C@@ -151,8 +151,10 @@ it "should return all topics metadata" $ \k -> do res <- allTopicsMetadata k (Timeout 1000) res `shouldSatisfy` isRight- (length . kmBrokers) <$> res `shouldBe` Right 1- (length . kmTopics) <$> res `shouldBe` Right 2+ let filterUserTopics m = m { kmTopics = filter (\t -> topicType (tmTopicName t) == User) (kmTopics m) }+ let res' = fmap filterUserTopics res+ length . kmBrokers <$> res' `shouldBe` Right 1+ length . kmTopics <$> res' `shouldBe` Right 1 it "should return topic metadata" $ \k -> do res <- topicMetadata k (Timeout 1000) testTopic
tests-it/Kafka/TestEnv.hs view
@@ -8,6 +8,7 @@ import Data.Monoid ((<>)) import System.Environment import System.IO.Unsafe+import qualified Data.Text as Text import Control.Concurrent import Kafka.Consumer as C@@ -16,13 +17,13 @@ import Test.Hspec brokerAddress :: BrokerAddress-brokerAddress = unsafePerformIO $- BrokerAddress <$> getEnv "KAFKA_TEST_BROKER" `catch` \(_ :: SomeException) -> (return "localhost:9092")+brokerAddress = unsafePerformIO $ do+ (BrokerAddress . Text.pack) <$> getEnv "KAFKA_TEST_BROKER" `catch` \(_ :: SomeException) -> (return "localhost:9092") {-# NOINLINE brokerAddress #-} testTopic :: TopicName-testTopic = unsafePerformIO $- TopicName <$> getEnv "KAFKA_TEST_TOPIC" `catch` \(_ :: SomeException) -> (return "kafka-client_tests")+testTopic = unsafePerformIO $ do+ (TopicName . Text.pack) <$> getEnv "KAFKA_TEST_TOPIC" `catch` \(_ :: SomeException) -> (return "kafka-client_tests") {-# NOINLINE testTopic #-} testGroupId :: ConsumerGroupId@@ -48,9 +49,7 @@ <> offsetReset Earliest mkProducer :: IO KafkaProducer-mkProducer = do- (Right p) <- newProducer producerProps- return p+mkProducer = newProducer producerProps >>= \(Right p) -> pure p mkConsumerWith :: ConsumerProperties -> IO KafkaConsumer mkConsumerWith props = do
tests/Kafka/Consumer/ConsumerRecordMapSpec.hs view
@@ -4,15 +4,16 @@ ) where import Data.Bitraversable+import Data.Text import Kafka.Consumer.Types import Kafka.Types import Test.Hspec -testKey, testValue :: String+testKey, testValue :: Text testKey = "some-key" testValue = "some-value" -testRecord :: ConsumerRecord (Maybe String) (Maybe String)+testRecord :: ConsumerRecord (Maybe Text) (Maybe Text) testRecord = ConsumerRecord { crTopic = TopicName "some-topic" , crPartition = PartitionId 0@@ -25,7 +26,7 @@ spec :: Spec spec = describe "Kafka.Consumer.ConsumerRecordSpec" $ do it "should exract key" $- sequenceFirst testRecord `shouldBe` Just (crMapKey (const testKey) testRecord)+ bitraverse id pure testRecord `shouldBe` Just (crMapKey (const testKey) testRecord) it "should extract value" $ sequence testRecord `shouldBe` Just (crMapValue (const testValue) testRecord)
tests/Kafka/Consumer/ConsumerRecordTraverseSpec.hs view
@@ -1,19 +1,21 @@ {-# LANGUAGE OverloadedStrings #-}+ module Kafka.Consumer.ConsumerRecordTraverseSpec ( spec ) where import Data.Bifunctor import Data.Bitraversable+import Data.Text import Kafka.Consumer.Types import Kafka.Types import Test.Hspec -testKey, testValue :: String+testKey, testValue :: Text testKey = "some-key" testValue = "some-value" -testRecord :: ConsumerRecord String String+testRecord :: ConsumerRecord Text Text testRecord = ConsumerRecord { crTopic = TopicName "some-topic" , crPartition = PartitionId 0@@ -29,18 +31,6 @@ liftNothing :: a -> Maybe a liftNothing _ = Nothing -liftValueM :: a -> Maybe (Either String a)-liftValueM = pure . pure--liftNothingM :: a -> Maybe (Either String a)-liftNothingM _ = Nothing--testError :: Either String a-testError = Left "test error"--liftErrorM :: a -> Maybe (Either String a)-liftErrorM _ = Just testError- spec :: Spec spec = describe "Kafka.Consumer.ConsumerRecordTraverseSpec" $ do it "should sequence" $ do@@ -65,23 +55,3 @@ bitraverse liftNothing liftValue testRecord `shouldBe` Nothing bitraverse liftValue liftNothing testRecord `shouldBe` Nothing - it "should traverse key (monadic)" $- traverseFirstM liftValueM testRecord `shouldBe` Just (Right testRecord)-- it "should traverse value (monadic)" $- traverseM liftValueM testRecord `shouldBe` Just (Right testRecord)-- it "should traverse KV (monadic)" $- bitraverseM liftValueM liftValueM testRecord `shouldBe` Just (Right testRecord)-- it "should traverse and report error (monadic)" $ do- traverseFirstM liftErrorM testRecord `shouldBe` Just testError- traverseM liftErrorM testRecord `shouldBe` Just testError- bitraverseM liftValueM liftErrorM testRecord `shouldBe` Just testError- bitraverseM liftErrorM liftValueM testRecord `shouldBe` Just testError-- it "should traverse and report empty container (monadic)" $ do- traverseFirstM liftNothingM testRecord `shouldBe` Nothing- traverseM liftNothingM testRecord `shouldBe` Nothing- bitraverseM liftValueM liftNothingM testRecord `shouldBe` Nothing- bitraverseM liftNothingM liftValueM testRecord `shouldBe` Nothing