amqp-worker-2.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 (..)
, Bind (..)
, Route
, key
, word
, any1
, many
, keyText
, fromBind
, toBind
, toBindKey
, RequireRoute
) 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 Route Comment
-- > commentsKey = key "posts" & word "new"
--
-- Binding keys can contain wildcards, only used for matching messages
--
-- > commentsKey :: Key Bind Comment
-- > commentsKey = key "posts" & any1 & word "comments" & many
newtype Key a msg = Key [Bind]
deriving (Eq, Show, Semigroup, Monoid)
data Route
data Bind
= Word Text
| Any
| Many
deriving (Eq, Show)
fromBind :: Bind -> Text
fromBind (Word t) = t
fromBind Any = "*"
fromBind Many = "#"
toBind :: Text -> Bind
toBind = Word
keyText :: Key a msg -> Text
keyText (Key ns) =
Text.intercalate "." . List.map fromBind $ ns
-- | Start a new routing key (can also be used for bindings)
--
-- > key "messages"
-- > -- matches "messages"
key :: Text -> Key Route msg
key t = Key [Word t]
-- | A specific word. Can be used to chain Routing keys or Binding keys
--
-- > key "messages" & word "new"
-- > -- matches "messages.new"
word :: Text -> Key a msg -> Key a msg
word w (Key ws) = Key $ ws ++ [toBind w]
-- | Match any one word. Equivalent to `*`. Converts to a Binding key and can no longer be used to publish messaages
--
-- > key "messages" & any1
-- > -- matches "messages.new"
-- > -- matches "messages.update"
any1 :: Key a msg -> Key Bind 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
--
-- > key "messages" & many
-- > -- matches "messages"
-- > -- matches "messages.new"
-- > -- matches "messages.1234.update"
many :: Key a msg -> Key Bind msg
many (Key ws) = Key (ws ++ [Many])
-- | We can convert Route Keys to Bind Keys safely, as they are usable for both publishing and binding
toBindKey :: Key a msg -> Key Bind msg
toBindKey (Key ws) = Key ws
-- | Custom error message when trying to publish to Binding keys
type family RequireRoute (a :: Type) :: Constraint where
RequireRoute Bind =
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"
)
RequireRoute a = ()