{-# LANGUAGE TypeData #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE UndecidableSuperClasses #-}
module Arbiter.Core.QueueRegistry
(
JobPayloadRegistry
, QueueSpec (..)
, Queue
, TableForPayload
, ResultFor
, SpecForPayload
, SpecName
, SpecPayload
, SpecResult
, AllQueuesUnique
, RegistryTables (..)
) where
import Data.Kind (Constraint, Type)
import Data.Proxy (Proxy (..))
import Data.Text (Text)
import Data.Text qualified as T
import GHC.TypeLits (ErrorMessage (..), KnownSymbol, Symbol, TypeError, symbolVal)
import Arbiter.Core.Job.Kind (HasKind (..))
type data QueueSpec = QueueWithResult Symbol Type Type
type Queue (table :: Symbol) (payload :: Type) = QueueWithResult table payload ()
type JobPayloadRegistry = [QueueSpec]
type family SpecName (spec :: QueueSpec) :: Symbol where
SpecName (QueueWithResult table _ _) = table
type family SpecPayload (spec :: QueueSpec) :: Type where
SpecPayload (QueueWithResult _ payload _) = payload
type family SpecResult (spec :: QueueSpec) :: Type where
SpecResult (QueueWithResult _ _ result) = result
type family SpecForPayload (payload :: Type) (registry :: JobPayloadRegistry) :: QueueSpec where
SpecForPayload payload registry = MatchIn payload '[] registry
type family
MatchIn (payload :: Type) (seen :: JobPayloadRegistry) (rest :: JobPayloadRegistry)
:: QueueSpec
where
MatchIn payload seen (QueueWithResult table payload result ': rest) =
OnlyMatch payload (QueueWithResult table payload result) (AppendSpecs seen rest)
MatchIn payload seen (spec ': rest) = MatchIn payload (spec ': seen) rest
MatchIn payload _ '[] = PayloadNotRegistered payload
type family
AppendSpecs (xs :: JobPayloadRegistry) (ys :: JobPayloadRegistry)
:: JobPayloadRegistry
where
AppendSpecs '[] ys = ys
AppendSpecs (x ': xs) ys = x ': AppendSpecs xs ys
type family
OnlyMatch (payload :: Type) (spec :: QueueSpec) (others :: JobPayloadRegistry)
:: QueueSpec
where
OnlyMatch _ spec '[] = spec
OnlyMatch payload _ (QueueWithResult _ payload _ ': _) = TypeError (DuplicatePayloadMsg payload)
OnlyMatch _ (QueueWithResult table _ _) (QueueWithResult table _ _ ': _) =
TypeError (DuplicateTableMsg table)
OnlyMatch payload spec (_ ': rest) = OnlyMatch payload spec rest
type DuplicatePayloadMsg (payload :: Type) =
'Text "Duplicate payload type in registry: " ':<>: 'ShowType payload
type DuplicateTableMsg (table :: Symbol) =
'Text "Duplicate table name in registry: " ':<>: 'ShowType table
type family PayloadNotRegistered (payload :: Type) :: QueueSpec where
PayloadNotRegistered payload =
TypeError
( 'Text "Payload type "
':<>: 'ShowType payload
':<>: 'Text " not found in registry"
':$$: 'Text "Add a Queue entry, or QueueWithResult to store a result."
)
type family TableForPayload (payload :: Type) (registry :: JobPayloadRegistry) :: Symbol where
TableForPayload payload registry = SpecName (SpecForPayload payload registry)
type family ResultFor (payload :: Type) (registry :: JobPayloadRegistry) :: Type where
ResultFor payload registry = SpecResult (SpecForPayload payload registry)
type family AllQueuesUnique (registry :: JobPayloadRegistry) :: Constraint where
AllQueuesUnique '[] = ()
AllQueuesUnique (spec ': rest) =
( NotInTables (SpecName spec) rest
, NotInPayloads (SpecPayload spec) rest
, AllQueuesUnique rest
)
type family NotInTables (table :: Symbol) (registry :: JobPayloadRegistry) :: Constraint where
NotInTables _ '[] = ()
NotInTables table (QueueWithResult table _ _ ': _) = TypeError (DuplicateTableMsg table)
NotInTables table (_ ': rest) = NotInTables table rest
type family NotInPayloads (payload :: Type) (registry :: JobPayloadRegistry) :: Constraint where
NotInPayloads _ '[] = ()
NotInPayloads payload (QueueWithResult _ payload _ ': _) = TypeError (DuplicatePayloadMsg payload)
NotInPayloads payload (_ ': rest) = NotInPayloads payload rest
class (AllQueuesUnique registry) => RegistryTables (registry :: JobPayloadRegistry) where
registryTableNames :: Proxy registry -> [Text]
registryQueueKinds :: Proxy registry -> [(Text, [Text])]
instance RegistryTables '[] where
registryTableNames :: Proxy '[] -> [Text]
registryTableNames Proxy '[]
_ = []
registryQueueKinds :: Proxy '[] -> [(Text, [Text])]
registryQueueKinds Proxy '[]
_ = []
instance
( HasKind (SpecPayload spec)
, KnownSymbol (SpecName spec)
, NotInPayloads (SpecPayload spec) rest
, NotInTables (SpecName spec) rest
, RegistryTables rest
)
=> RegistryTables (spec ': rest)
where
registryTableNames :: Proxy (spec : rest) -> [Text]
registryTableNames Proxy (spec : rest)
_ =
String -> Text
T.pack (Proxy (SpecName spec) -> String
forall (n :: Symbol) (proxy :: Symbol -> *).
KnownSymbol n =>
proxy n -> String
symbolVal (forall {k} (t :: k). Proxy t
forall (t :: Symbol). Proxy t
Proxy @(SpecName spec))) Text -> [Text] -> [Text]
forall a. a -> [a] -> [a]
: Proxy rest -> [Text]
forall (registry :: JobPayloadRegistry).
RegistryTables registry =>
Proxy registry -> [Text]
registryTableNames (forall (t :: JobPayloadRegistry). Proxy t
forall {k} (t :: k). Proxy t
Proxy @rest)
registryQueueKinds :: Proxy (spec : rest) -> [(Text, [Text])]
registryQueueKinds Proxy (spec : rest)
_ =
(String -> Text
T.pack (Proxy (SpecName spec) -> String
forall (n :: Symbol) (proxy :: Symbol -> *).
KnownSymbol n =>
proxy n -> String
symbolVal (forall {k} (t :: k). Proxy t
forall (t :: Symbol). Proxy t
Proxy @(SpecName spec))), forall payload. HasKind payload => [Text]
kindsFor @(SpecPayload spec))
(Text, [Text]) -> [(Text, [Text])] -> [(Text, [Text])]
forall a. a -> [a] -> [a]
: Proxy rest -> [(Text, [Text])]
forall (registry :: JobPayloadRegistry).
RegistryTables registry =>
Proxy registry -> [(Text, [Text])]
registryQueueKinds (forall (t :: JobPayloadRegistry). Proxy t
forall {k} (t :: k). Proxy t
Proxy @rest)