{-# LANGUAGE UndecidableInstances #-} module Ema.Route.Generic.Verification ( type VerifyModels, type VerifyRoutes, ) where import Data.Generics.Product.Any (HasAny) import GHC.TypeLits {- | @VerifyModels model routeModels lookups@ verifies the given @model@ to ensure that there exists a valid @HasSubModels@ instance for the given combination of (model, routeModels, lookups). -} type family VerifyModels model (subModels :: [Type]) (lookups :: [Type]) :: Constraint where VerifyModels m '[] '[] = () VerifyModels m '[] t = TypeError ( 'Text "'WithSubModels' has extra unnecessary types: " ':$$: 'Text "" ':$$: 'Text "\t" ':<>: 'ShowType t) VerifyModels m f '[] = TypeError ( 'Text "'WithSubModels' is missing submodel types: " ':$$: 'Text "" ':$$: 'Text "\t" ':<>: 'ShowType f) VerifyModels model (model ': fs) (model ': ss) = -- This checks the simple case that (the model ~ the submodel), -- because it doesn't necessarily have to have a generic instance in this case. -- After that, we can /assume/ the model has a generic instance to allow us to inspect its -- structure statically to verify that the correct submodel exists. VerifyModels model fs ss VerifyModels model (subModel ': subModels) (sel ': sels) = ( HasAny sel model model subModel subModel , VerifyModels model subModels sels ) {- | @VerifyRoutes route rep subroutes@ verifies the given @route@ to ensure that there exists a valid @HasSubRoutes@ instance for @route@ given its @rep@ and the @subroutes@ it is generic-deriving from. Invariant: code ~ Code route -} type family VerifyRoutes (rcode :: [Type]) (subRoutes :: [Type]) :: Constraint where VerifyRoutes '[] '[] = () -- Inconsistent lengths VerifyRoutes '[] t = TypeError ( 'Text "'WithSubRoutes' has extra unnecessary types: " ':$$: 'Text "" ':$$: 'Text "\t" ':<>: 'ShowType t) VerifyRoutes t '[] = TypeError ( 'Text "'WithSubRoutes' is missing subroutes for:" ':$$: 'Text "" ':$$: ( 'Text "\t" ':<>: 'ShowType t) ) -- Subroute rep is unit (REVIEW: this case not strictly necessary anymore; should it be removed?) VerifyRoutes (() ': rs) (() : rs') = VerifyRoutes rs rs' VerifyRoutes (r' ': rs) (() : rs') = TypeError ( 'Text "A 'WithSubRoutes' entry is '()' instead of the expected: " ':$$: 'ShowType r' ) VerifyRoutes (r1 ': rs) (r2 ': rs') = (Coercible r1 r2, VerifyRoutes rs rs')