packages feed

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 = ()