amqp-worker-1.0.0: src/Network/AMQP/Worker/Key.hs
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
module Network.AMQP.Worker.Key
( Key (..)
, Binding (..)
, Routing
, key
, word
, any1
, many
, keyText
, fromBind
, toBind
, toBindingKey
, RequireRouting
) where
import Data.Kind (Constraint, Type)
import qualified Data.List as List
import Data.Text (Text)
import qualified Data.Text as Text
import GHC.TypeLits (ErrorMessage (..), TypeError)
-- | Messages are published with a specific identifier called a Routing key. Queues can use Binding Keys to control which messages are delivered to them.
--
-- Routing keys have no dynamic component and can be used to publish messages
--
-- > commentsKey :: Key Routing Comment
-- > commentsKey = key "posts" & word "new"
--
-- Binding keys can contain wildcards, only used for matching messages
--
-- > commentsKey :: Key Binding Comment
-- > commentsKey = key "posts" & any1 & word "comments" & many
newtype Key a msg = Key [Binding]
deriving (Eq, Show, Semigroup, Monoid)
data Routing
data Binding
= Word Text
| Any
| Many
deriving (Eq, Show)
fromBind :: Binding -> Text
fromBind (Word t) = t
fromBind Any = "*"
fromBind Many = "#"
toBind :: Text -> Binding
toBind = Word
keyText :: Key a msg -> Text
keyText (Key ns) =
Text.intercalate "." . List.map fromBind $ ns
-- | Match any one word. Equivalent to `*`. Converts to a Binding key and can no longer be used to publish messaages
any1 :: Key a msg -> Key Binding msg
any1 (Key ws) = Key (ws ++ [Any])
-- | Match zero or more words. Equivalient to `#`. Converts to a Binding key and can no longer be used to publish messages
many :: Key a msg -> Key Binding msg
many (Key ws) = Key (ws ++ [Many])
-- | A specific word. Can be used to chain Routing keys or Binding keys
word :: Text -> Key a msg -> Key a msg
word w (Key ws) = Key $ ws ++ [toBind w]
-- | Start a new routing key (can also be used for bindings)
key :: Text -> Key Routing msg
key t = Key [Word t]
-- | We can convert Routing Keys to Binding Keys safely, as they are usable for both publishing and binding
toBindingKey :: Key a msg -> Key Binding msg
toBindingKey (Key ws) = Key ws
-- | Custom error message when trying to publish to Binding keys
type family RequireRouting (a :: Type) :: Constraint where
RequireRouting Binding =
TypeError
( 'Text "Expected Routing Key but got Binding Key instead. Messages can be published only with keys that exclusivlely use `key` and `word`"
:$$: 'Text "\n key \"message\" & word \"new\" \n"
)
RequireRouting a = ()