{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE QuantifiedConstraints #-}
{-# LANGUAGE RequiredTypeArguments #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE UndecidableSuperClasses #-}
module Hindsight.Events (
Event,
EventConstraints,
SomeLatestEvent (..),
mkEvent,
getEventName,
MaxVersion,
Versions,
CurrentPayloadType,
EventVersionCount,
EventVersionVector,
FullVersionRange,
EventVersions (..),
FromList,
FinalVersionType,
PayloadVersion,
PayloadAtVersion,
Upcast (..),
MigrateVersion (..),
MaxVersionPeano,
Serializable,
parseMap,
parseMapFromProxy,
getMaxVersion,
PeanoNat (..),
ReifiablePeanoNat (..),
ToPeanoNat,
FromPeanoNat,
Dict (..),
VersionConstraints (..),
ValidPayloadForVersion (..),
HasEvidenceList,
HasFullEvidenceList,
getPayloadEvidence,
VersionPayloadRequirements,
PeanoEqual,
)
where
import Data.Aeson (FromJSON (parseJSON), ToJSON, Value)
import Data.Aeson.Types qualified as Aeson
import Data.Kind (Constraint, Type)
import Data.Map (Map)
import Data.Map qualified as Map
import Data.Text qualified as T
import Data.Typeable (Proxy (..), Typeable)
import GHC.TypeLits (
ErrorMessage (..),
KnownSymbol,
Nat,
Symbol,
TypeError,
symbolVal,
type (+),
)
import Hindsight.Events.Internal.TypeLevel (
AssertPeanoEqual,
Dict (..),
FromPeanoNat,
ListLength,
PeanoEqual,
PeanoNat (..),
ReifiablePeanoNat (..),
ToPeanoNat,
)
import Hindsight.Events.Internal.Versioning (
EventVersions (..),
FinalVersionType,
FromList,
HasEvidenceList (..),
PayloadAtVersion,
VersionConstraints (..),
)
getEventName ::
forall (event :: Symbol).
(KnownSymbol event) =>
Proxy event ->
T.Text
getEventName :: forall (event :: Symbol). KnownSymbol event => Proxy event -> Text
getEventName Proxy event
_ = String -> Text
T.pack (String -> Text) -> String -> Text
forall a b. (a -> b) -> a -> b
$ Proxy event -> String
forall (n :: Symbol) (proxy :: Symbol -> *).
KnownSymbol n =>
proxy n -> String
symbolVal (forall {k} (t :: k). Proxy t
forall (t :: Symbol). Proxy t
Proxy @event)
type family MaxVersion (event :: Symbol) :: Nat
type family Versions (event :: Symbol) :: [Type]
type EventVersionCount event = 'PeanoSucc (ToPeanoNat (MaxVersion event))
type family EventVersionVector (event :: Symbol) :: EventVersions 'PeanoZero (EventVersionCount event) where
EventVersionVector event = FromList (Versions event)
type FullVersionRange event =
HasEvidenceList 'PeanoZero (EventVersionCount event) event ValidPayloadForVersion (EventVersionVector event)
type VersionCountMismatchError :: Symbol -> ErrorMessage
type VersionCountMismatchError event =
'Text "Version count mismatch for event '"
':<>: 'Text event
':<>: 'Text "'"
':$$: 'Text " MaxVersion declares "
':<>: 'ShowType (FromPeanoNat (EventVersionCount event))
':<>: 'Text " versions"
':$$: 'Text " But Versions list has "
':<>: 'ShowType (FromPeanoNat (ListLength (Versions event)))
':<>: 'Text " elements"
':$$: 'Text ""
':$$: 'Text "Hint: Check that (MaxVersion event + 1) matches the length of your Versions list"
type AssertVersionCountMatches :: Symbol -> Constraint
type AssertVersionCountMatches event =
AssertPeanoEqual
(ListLength (Versions event))
(EventVersionCount event)
(VersionCountMismatchError event)
class (EventConstraints event) => Event (event :: Symbol)
type EventConstraints (event :: Symbol) =
( AssertVersionCountMatches event
, KnownSymbol event
, Typeable event
, ToJSON (CurrentPayloadType event)
, FullVersionRange event
, ReifiablePeanoNat (ToPeanoNat (MaxVersion event))
)
type CurrentPayloadType :: Symbol -> Type
type CurrentPayloadType event = FinalVersionType (EventVersionVector event)
type PayloadVersion event n = PayloadAtVersion n (EventVersionVector event)
type family MaxVersionPeano (event :: Symbol) :: PeanoNat where
MaxVersionPeano event = ToPeanoNat (MaxVersion event)
type family IsLatest (ver :: PeanoNat) (event :: Symbol) :: Bool where
IsLatest ver event = PeanoEqual ver (MaxVersionPeano event) 'True 'False
class Upcast (ver :: Nat) (event :: Symbol) where
upcast ::
PayloadAtVersion (ToPeanoNat ver) (EventVersionVector event) ->
PayloadAtVersion (ToPeanoNat (ver + 1)) (EventVersionVector event)
instance
{-# OVERLAPPABLE #-}
( TypeError
( 'Text "Missing Upcast instance for version "
':<>: 'ShowType ver
':<>: 'Text " of event \""
':<>: 'Text event
':<>: 'Text "\""
':$$: 'Text ""
':$$: 'Text "You need to define:"
':$$: 'Text " instance Upcast "
':<>: 'ShowType ver
':<>: 'Text " \""
':<>: 'Text event
':<>: 'Text "\" where"
':$$: 'Text " upcast v"
':<>: 'ShowType ver
':<>: 'Text " = ..."
':$$: 'Text ""
':$$: 'Text "This upgrades from version "
':<>: 'ShowType ver
':<>: 'Text " to version "
':<>: 'ShowType (ver + 1)
)
) =>
Upcast ver event
where
upcast :: PayloadAtVersion (ToPeanoNat ver) (EventVersionVector event)
-> PayloadAtVersion
(ToPeanoNat (ver + 1)) (EventVersionVector event)
upcast = String
-> PayloadAtVersion (ToPeanoNat ver) (FromList (Versions event))
-> PayloadAtVersion
(ToPeanoNat (ver + 1)) (FromList (Versions event))
forall a. HasCallStack => String -> a
error String
"unreachable: TypeError should prevent compilation"
class MigrateVersion (ver :: Nat) (event :: Symbol) where
migrateVersion ::
PayloadAtVersion (ToPeanoNat ver) (EventVersionVector event) ->
CurrentPayloadType event
default migrateVersion ::
(ConsecutiveUpcast (IsLatest (ToPeanoNat ver) event) (ToPeanoNat ver) event) =>
PayloadAtVersion (ToPeanoNat ver) (EventVersionVector event) ->
CurrentPayloadType event
migrateVersion = forall (isLatest :: Bool) (ver :: PeanoNat) (event :: Symbol).
ConsecutiveUpcast isLatest ver event =>
PayloadAtVersion ver (EventVersionVector event)
-> CurrentPayloadType event
viaConsecutive @(IsLatest (ToPeanoNat ver) event) @(ToPeanoNat ver) @event
class ConsecutiveUpcast (isLatest :: Bool) (ver :: PeanoNat) (event :: Symbol) where
viaConsecutive ::
PayloadAtVersion ver (EventVersionVector event) ->
CurrentPayloadType event
instance
( Upcast (FromPeanoNat ver) event
, ConsecutiveUpcast (IsLatest ('PeanoSucc ver) event) ('PeanoSucc ver) event
,
PayloadAtVersion (ToPeanoNat (FromPeanoNat ver)) (EventVersionVector event)
~ PayloadAtVersion ver (EventVersionVector event)
, PayloadAtVersion (ToPeanoNat (FromPeanoNat ver + 1)) (EventVersionVector event)
~ PayloadAtVersion ('PeanoSucc ver) (EventVersionVector event)
) =>
ConsecutiveUpcast 'False ver event
where
viaConsecutive :: PayloadAtVersion ver (EventVersionVector event)
-> CurrentPayloadType event
viaConsecutive = forall (isLatest :: Bool) (ver :: PeanoNat) (event :: Symbol).
ConsecutiveUpcast isLatest ver event =>
PayloadAtVersion ver (EventVersionVector event)
-> CurrentPayloadType event
viaConsecutive @(IsLatest ('PeanoSucc ver) event) @('PeanoSucc ver) @event (PayloadAtVersion ('PeanoSucc ver) (FromList (Versions event))
-> FinalVersionType (FromList (Versions event)))
-> (PayloadAtVersion ver (FromList (Versions event))
-> PayloadAtVersion ('PeanoSucc ver) (FromList (Versions event)))
-> PayloadAtVersion ver (FromList (Versions event))
-> FinalVersionType (FromList (Versions event))
forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (ver :: Natural) (event :: Symbol).
Upcast ver event =>
PayloadAtVersion (ToPeanoNat ver) (EventVersionVector event)
-> PayloadAtVersion
(ToPeanoNat (ver + 1)) (EventVersionVector event)
upcast @(FromPeanoNat ver) @event
instance
( ver ~ MaxVersionPeano event
, PayloadAtVersion ver (EventVersionVector event) ~ CurrentPayloadType event
) =>
ConsecutiveUpcast 'True ver event
where
viaConsecutive :: PayloadAtVersion ver (EventVersionVector event)
-> CurrentPayloadType event
viaConsecutive = FinalVersionType (FromList (Versions event))
-> FinalVersionType (FromList (Versions event))
PayloadAtVersion ver (EventVersionVector event)
-> CurrentPayloadType event
forall a. a -> a
id
type VersionPayloadRequirements :: Symbol -> PeanoNat -> Type -> Constraint
type VersionPayloadRequirements event idx payload =
( Serializable payload
, MigrateVersion (FromPeanoNat idx) event
,
PayloadVersion event (ToPeanoNat (FromPeanoNat idx)) ~ PayloadVersion event idx
, KnownSymbol event
, ReifiablePeanoNat idx
, Typeable payload
, Typeable event
, Typeable idx
, payload ~ PayloadVersion event idx
)
class (VersionPayloadRequirements event idx payload) => ValidPayloadForVersion (event :: Symbol) (idx :: PeanoNat) (payload :: Type) where
constraintEvidence :: Dict (VersionPayloadRequirements event idx payload)
instance (VersionPayloadRequirements event idx payload) => ValidPayloadForVersion event idx payload where
constraintEvidence :: Dict (VersionPayloadRequirements event idx payload)
constraintEvidence = Dict
(Serializable payload, MigrateVersion (FromPeanoNat idx) event,
payload ~ payload, KnownSymbol event, ReifiablePeanoNat idx,
Typeable payload, Typeable event, Typeable idx, payload ~ payload)
Dict (VersionPayloadRequirements event idx payload)
forall (c :: Constraint). c => Dict c
Dict
type HasFullEvidenceList event c = (HasEvidenceList 'PeanoZero (EventVersionCount event) event c (EventVersionVector event))
getPayloadEvidence :: forall event c. (HasFullEvidenceList event c) => VersionConstraints (EventVersionVector event) (c event)
getPayloadEvidence :: forall (event :: Symbol)
(c :: Symbol -> PeanoNat -> * -> Constraint).
HasFullEvidenceList event c =>
VersionConstraints (EventVersionVector event) (c event)
getPayloadEvidence = VersionConstraints (FromList (Versions event)) (c event)
VersionConstraints (EventVersionVector event) (c event)
forall k (startsAt :: PeanoNat) (finalCount :: PeanoNat)
(event :: k) (c :: k -> PeanoNat -> * -> Constraint)
(vec :: EventVersions startsAt finalCount).
HasEvidenceList startsAt finalCount event c vec =>
VersionConstraints vec (c event)
getEvidenceList
type Serializable a = (Show a, Eq a, FromJSON a, ToJSON a)
data SomeLatestEvent = forall event. (Event event) => SomeLatestEvent {()
getEventProxy :: Proxy event, ()
getPayload :: CurrentPayloadType event}
mkEvent ::
forall (event :: Symbol) ->
(Event event) =>
CurrentPayloadType event ->
SomeLatestEvent
mkEvent :: forall (event :: Symbol) ->
Event event => CurrentPayloadType event -> SomeLatestEvent
mkEvent event CurrentPayloadType event
payload = Proxy event -> CurrentPayloadType event -> SomeLatestEvent
forall (event :: Symbol).
Event event =>
Proxy event -> CurrentPayloadType event -> SomeLatestEvent
SomeLatestEvent (forall {k} (t :: k). Proxy t
forall (t :: Symbol). Proxy t
Proxy @event) CurrentPayloadType event
payload
parseMap ::
forall event.
(Event event) =>
Map Int (Value -> Aeson.Parser (CurrentPayloadType event))
parseMap :: forall (event :: Symbol).
Event event =>
Map Int (Value -> Parser (CurrentPayloadType event))
parseMap = [(Int, Value -> Parser (CurrentPayloadType event))]
-> Map Int (Value -> Parser (CurrentPayloadType event))
forall k a. Ord k => [(k, a)] -> Map k a
Map.fromList ([(Int, Value -> Parser (CurrentPayloadType event))]
-> Map Int (Value -> Parser (CurrentPayloadType event)))
-> [(Int, Value -> Parser (CurrentPayloadType event))]
-> Map Int (Value -> Parser (CurrentPayloadType event))
forall a b. (a -> b) -> a -> b
$ [(Int, Value -> Parser (CurrentPayloadType event))]
-> VersionConstraints
(FromList (Versions event)) (ValidPayloadForVersion event)
-> [(Int, Value -> Parser (CurrentPayloadType event))]
forall {startsAt :: PeanoNat} {finalCount :: PeanoNat}
(ts :: EventVersions startsAt finalCount).
[(Int, Value -> Parser (CurrentPayloadType event))]
-> VersionConstraints ts (ValidPayloadForVersion event)
-> [(Int, Value -> Parser (CurrentPayloadType event))]
go [] (forall (event :: Symbol)
(c :: Symbol -> PeanoNat -> * -> Constraint).
HasFullEvidenceList event c =>
VersionConstraints (EventVersionVector event) (c event)
getPayloadEvidence @event @ValidPayloadForVersion)
where
go :: forall ts. [(Int, Value -> Aeson.Parser (CurrentPayloadType event))] -> VersionConstraints ts (ValidPayloadForVersion event) -> [(Int, Value -> Aeson.Parser (CurrentPayloadType event))]
go :: forall {startsAt :: PeanoNat} {finalCount :: PeanoNat}
(ts :: EventVersions startsAt finalCount).
[(Int, Value -> Parser (CurrentPayloadType event))]
-> VersionConstraints ts (ValidPayloadForVersion event)
-> [(Int, Value -> Parser (CurrentPayloadType event))]
go [(Int, Value -> Parser (CurrentPayloadType event))]
acc (VersionConstraintsLast (Proxy startsAt
_pVer :: Proxy ver, Proxy t
_pPayload :: Proxy payload)) =
let ver :: Int
ver = Integer -> Int
forall a. Num a => Integer -> a
fromInteger (Integer -> Int) -> Integer -> Int
forall a b. (a -> b) -> a -> b
$ forall (n :: PeanoNat). ReifiablePeanoNat n => Integer
reifyPeanoNat @ver
parser :: Value -> Parser (FinalVersionType (FromList (Versions event)))
parser = \Value
v -> forall (ver :: Natural) (event :: Symbol).
MigrateVersion ver event =>
PayloadAtVersion (ToPeanoNat ver) (EventVersionVector event)
-> CurrentPayloadType event
migrateVersion @(FromPeanoNat ver) @event (t -> FinalVersionType (FromList (Versions event)))
-> Parser t
-> Parser (FinalVersionType (FromList (Versions event)))
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> forall a. FromJSON a => Value -> Parser a
parseJSON @payload Value
v
in (Int
ver, Value -> Parser (FinalVersionType (FromList (Versions event)))
parser) (Int,
Value -> Parser (FinalVersionType (FromList (Versions event))))
-> [(Int,
Value -> Parser (FinalVersionType (FromList (Versions event))))]
-> [(Int,
Value -> Parser (FinalVersionType (FromList (Versions event))))]
forall a. a -> [a] -> [a]
: [(Int,
Value -> Parser (FinalVersionType (FromList (Versions event))))]
[(Int, Value -> Parser (CurrentPayloadType event))]
acc
go [(Int, Value -> Parser (CurrentPayloadType event))]
acc (VersionConstraintsCons (Proxy startsAt
_pVer :: Proxy ver, Proxy t
_pPayload :: Proxy payload) VersionConstraints ts' (ValidPayloadForVersion event)
rest) =
let ver :: Int
ver = Integer -> Int
forall a. Num a => Integer -> a
fromInteger (Integer -> Int) -> Integer -> Int
forall a b. (a -> b) -> a -> b
$ forall (n :: PeanoNat). ReifiablePeanoNat n => Integer
reifyPeanoNat @ver
parser :: Value -> Parser (FinalVersionType (FromList (Versions event)))
parser = \Value
v -> forall (ver :: Natural) (event :: Symbol).
MigrateVersion ver event =>
PayloadAtVersion (ToPeanoNat ver) (EventVersionVector event)
-> CurrentPayloadType event
migrateVersion @(FromPeanoNat ver) @event (t -> FinalVersionType (FromList (Versions event)))
-> Parser t
-> Parser (FinalVersionType (FromList (Versions event)))
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> forall a. FromJSON a => Value -> Parser a
parseJSON @payload Value
v
in [(Int, Value -> Parser (CurrentPayloadType event))]
-> VersionConstraints ts' (ValidPayloadForVersion event)
-> [(Int, Value -> Parser (CurrentPayloadType event))]
forall {startsAt :: PeanoNat} {finalCount :: PeanoNat}
(ts :: EventVersions startsAt finalCount).
[(Int, Value -> Parser (CurrentPayloadType event))]
-> VersionConstraints ts (ValidPayloadForVersion event)
-> [(Int, Value -> Parser (CurrentPayloadType event))]
go ((Int
ver, Value -> Parser (FinalVersionType (FromList (Versions event)))
parser) (Int,
Value -> Parser (FinalVersionType (FromList (Versions event))))
-> [(Int,
Value -> Parser (FinalVersionType (FromList (Versions event))))]
-> [(Int,
Value -> Parser (FinalVersionType (FromList (Versions event))))]
forall a. a -> [a] -> [a]
: [(Int,
Value -> Parser (FinalVersionType (FromList (Versions event))))]
[(Int, Value -> Parser (CurrentPayloadType event))]
acc) VersionConstraints ts' (ValidPayloadForVersion event)
rest
parseMapFromProxy ::
forall event.
(Event event) =>
Proxy event ->
Map Int (Value -> Aeson.Parser (CurrentPayloadType event))
parseMapFromProxy :: forall (event :: Symbol).
Event event =>
Proxy event -> Map Int (Value -> Parser (CurrentPayloadType event))
parseMapFromProxy Proxy event
_ = forall (event :: Symbol).
Event event =>
Map Int (Value -> Parser (CurrentPayloadType event))
parseMap @event
getMaxVersion ::
forall event.
(Event event) =>
Proxy event ->
Integer
getMaxVersion :: forall (event :: Symbol). Event event => Proxy event -> Integer
getMaxVersion Proxy event
_ = forall (n :: PeanoNat). ReifiablePeanoNat n => Integer
reifyPeanoNat @(ToPeanoNat (MaxVersion event))