packages feed

ema-0.8.0.0: src/Ema/Route/Lib/Multi.hs

{-# OPTIONS_GHC -Wno-orphans #-}

{- | Merging multiple Ema sites into one.

    This is implemented in using `sop-core`'s NS and NP types. Use as
    `MultiRoute '[MySite1, MySite2, ...]`.
-}
module Ema.Route.Lib.Multi (
  MultiRoute,
  MultiModel,
) where

import Data.SOP (I (..), NP (..), NS (..))
import Ema.Route.Class (IsRoute (..))
import Ema.Route.Prism
import Ema.Site (EmaSite (..), EmaStaticSite)
import Optics.Core (equality, iso, prism', (%))

{- | The merged site's route is represented as a n-ary sum (`NS`) of the
 sub-routes.
-}
type MultiRoute (rs :: [Type]) = NS I rs

type family MultiModel (rs :: [Type]) :: [Type] where
  MultiModel '[] = '[]
  MultiModel (r ': rs) = RouteModel r : MultiModel rs

type family MultiSiteArg (rs :: [Type]) :: [Type] where
  MultiSiteArg '[] = '[]
  MultiSiteArg (r ': rs) = SiteArg r : MultiSiteArg rs

instance IsRoute (MultiRoute '[]) where
  type RouteModel (MultiRoute '[]) = NP I '[]
  routePrism = impossiblePrism
    where
      impossiblePrism :: (NP I '[] -> Prism_ FilePath (MultiRoute '[]))
      impossiblePrism Nil =
        toPrism_ $ prism' (\case {}) (const Nothing)
  routeUniverse Nil = mempty

instance
  ( IsRoute r
  , IsRoute (MultiRoute rs)
  , RouteModel (MultiRoute rs) ~ NP I (MultiModel rs)
  ) =>
  IsRoute (MultiRoute (r ': rs))
  where
  type RouteModel (MultiRoute (r ': rs)) = NP I (RouteModel r ': MultiModel rs)
  routePrism =
    routePrism @r
      `nsRoutePrism` routePrism @(MultiRoute rs)
  routeUniverse (I m :* ms) =
    fmap (toNS . Left) (routeUniverse @r m)
      <> fmap (toNS . Right) (routeUniverse @(MultiRoute rs) ms)

instance EmaSite (MultiRoute '[]) where
  type SiteArg (MultiRoute '[]) = NP I '[]
  siteInput _ Nil = pure $ pure Nil
  siteOutput _ Nil = \case {}

instance
  ( EmaStaticSite r
  , EmaStaticSite (MultiRoute rs)
  , SiteArg (MultiRoute rs) ~ NP I (MultiSiteArg rs)
  , RouteModel (MultiRoute rs) ~ NP I (MultiModel rs)
  ) =>
  EmaSite (MultiRoute (r ': rs))
  where
  type SiteArg (MultiRoute (r ': rs)) = NP I (MultiSiteArg (r ': rs))
  siteInput cliAct (I i :* is) = do
    m <- siteInput @r cliAct i
    ms <- siteInput @(MultiRoute rs) cliAct is
    pure $ curry toNP <$> m <*> ms
  siteOutput rp (I m :* ms) =
    fromNS
      >>> either
        (siteOutput @r (rp % headRoute) m)
        (siteOutput @(MultiRoute rs) (rp % tailRoute) ms)
    where
      tailRoute =
        (prism' (toNS . Right) (fromNS >>> rightToMaybe))
      headRoute =
        (prism' (toNS . Left) (fromNS >>> leftToMaybe))

-- | Like `eitherRoutePrism` but uses sop-core types instead of Either/Product.
nsRoutePrism ::
  (a -> Prism_ FilePath r) ->
  (NP I as -> Prism_ FilePath (NS I rs)) ->
  (NP I (a ': as) -> Prism_ FilePath (NS I (r ': rs)))
nsRoutePrism a b =
  eitherRoutePrism a b
    & mapRoutePrism equality (iso toNS fromNS) fromNP

fromNP :: NP I (a ': as) -> (a, NP I as)
fromNP (I x :* y) = (x, y)

toNP :: (a, NP I as) -> NP I (a ': as)
toNP (x, y) = I x :* y

fromNS :: NS I (a ': as) -> Either a (NS I as)
fromNS = \case
  Z (I x) -> Left x
  S xs -> Right xs

toNS :: Either a (NS I as) -> NS I (a ': as)
toNS = either (Z . I) S