{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
module Miso.Router
( runRoute
, route
, HasRouter
, RouteT
, RoutingError (..)
) where
import qualified Data.ByteString.Char8 as BS
import Data.Proxy
import Data.Text (Text)
import qualified Data.Text as T
import Data.Text.Encoding
import GHC.TypeLits
import Network.HTTP.Types hiding (Header)
import Network.URI
import Servant.API
import Web.HttpApiData
import Miso.Html hiding (text)
data Location = Location
{ locPath :: [Text]
, locQuery :: Query
} deriving (Show, Eq, Ord)
data RoutingError = Fail
deriving (Show, Eq, Ord)
data Router a where
RChoice :: Router a -> Router a -> Router a
RCapture :: FromHttpApiData x => (x -> Router a) -> Router a
RQueryParam :: (FromHttpApiData x, KnownSymbol sym)
=> Proxy sym -> (Maybe x -> Router a) -> Router a
RQueryParams :: (FromHttpApiData x, KnownSymbol sym)
=> Proxy sym -> ([x] -> Router a) -> Router a
RQueryFlag :: KnownSymbol sym
=> Proxy sym -> (Bool -> Router a) -> Router a
RPath :: KnownSymbol sym => Proxy sym -> Router a -> Router a
RPage :: a -> Router a
class HasRouter layout where
type RouteT layout a :: *
mkRouter :: Proxy layout -> Proxy a -> RouteT layout a -> Router a
instance (HasRouter x, HasRouter y) => HasRouter (x :<|> y) where
type RouteT (x :<|> y) a = RouteT x a :<|> RouteT y a
mkRouter _ (a :: Proxy a) ((x :: RouteT x a) :<|> (y :: RouteT y a))
= RChoice (mkRouter (Proxy :: Proxy x) a x) (mkRouter (Proxy :: Proxy y) a y)
instance (HasRouter sublayout, FromHttpApiData x) =>
HasRouter (Capture sym x :> sublayout) where
type RouteT (Capture sym x :> sublayout) a = x -> RouteT sublayout a
mkRouter _ a f = RCapture (\x -> mkRouter (Proxy :: Proxy sublayout) a (f x))
instance (HasRouter sublayout, FromHttpApiData x, KnownSymbol sym)
=> HasRouter (QueryParam sym x :> sublayout) where
type RouteT (QueryParam sym x :> sublayout) a = Maybe x -> RouteT sublayout a
mkRouter _ a f = RQueryParam (Proxy :: Proxy sym)
(\x -> mkRouter (Proxy :: Proxy sublayout) a (f x))
instance (HasRouter sublayout, FromHttpApiData x, KnownSymbol sym)
=> HasRouter (QueryParams sym x :> sublayout) where
type RouteT (QueryParams sym x :> sublayout) a = [x] -> RouteT sublayout a
mkRouter _ a f = RQueryParams
(Proxy :: Proxy sym)
(\x -> mkRouter (Proxy :: Proxy sublayout) a (f x))
instance (HasRouter sublayout, KnownSymbol sym)
=> HasRouter (QueryFlag sym :> sublayout) where
type RouteT (QueryFlag sym :> sublayout) a = Bool -> RouteT sublayout a
mkRouter _ a f = RQueryFlag
(Proxy :: Proxy sym)
(\x -> mkRouter (Proxy :: Proxy sublayout) a (f x))
instance HasRouter sublayout => HasRouter (Header sym (x :: *) :> sublayout) where
type RouteT (Header sym x :> sublayout) a = Maybe x -> RouteT sublayout a
mkRouter _ a f = mkRouter (Proxy :: Proxy sublayout) a (f Nothing)
instance (HasRouter sublayout, KnownSymbol path)
=> HasRouter (path :> sublayout) where
type RouteT (path :> sublayout) a = RouteT sublayout a
mkRouter _ a page = RPath
(Proxy :: Proxy path)
(mkRouter (Proxy :: Proxy sublayout) a page)
instance HasRouter (View a) where
type RouteT (View a) x = x
mkRouter _ _ a = RPage a
instance HasRouter (Verb m s c a) where
type RouteT (Verb m s c a) x = x
mkRouter _ _ a = RPage a
instance HasRouter Raw where
type RouteT Raw x = x
mkRouter _ _ a = RPage a
runRouteLoc :: forall layout a. HasRouter layout
=> Location -> Proxy layout -> RouteT layout a -> Either RoutingError a
runRouteLoc loc layout page =
let routing = mkRouter layout (Proxy :: Proxy a) page
in routeLoc loc routing
route
:: HasRouter layout
=> Proxy layout
-> RouteT layout a
-> URI
-> Either RoutingError a
route layout handler u = runRouteLoc (uriToLocation u) layout handler
runRoute
:: HasRouter layout
=> Proxy layout
-> RouteT layout (m -> a)
-> (m -> URI)
-> m
-> Either RoutingError a
runRoute layout pages getURI model = ($ model) <$> route layout pages (getURI model)
routeLoc :: Location -> Router a -> Either RoutingError a
routeLoc loc r = case r of
RChoice a b -> do
case routeLoc loc a of
Left Fail -> routeLoc loc b
Right x -> Right x
RCapture f -> case locPath loc of
[] -> Left Fail
capture:paths ->
case parseUrlPieceMaybe capture of
Nothing -> Left Fail
Just x -> routeLoc loc { locPath = paths } (f x)
RQueryParam sym f -> case lookup (BS.pack $ symbolVal sym) (locQuery loc) of
Nothing -> routeLoc loc (f Nothing)
Just Nothing -> Left Fail
Just (Just text) -> case parseQueryParamMaybe (decodeUtf8 text) of
Nothing -> Left Fail
Just x -> routeLoc loc (f (Just x))
RQueryParams sym f -> maybe (Left Fail) (\x -> routeLoc loc (f x)) $ do
ps <- sequence $ snd <$> Prelude.filter
(\(k, _) -> k == BS.pack (symbolVal sym)) (locQuery loc)
sequence $ (parseQueryParamMaybe . decodeUtf8) <$> ps
RQueryFlag sym f -> case lookup (BS.pack $ symbolVal sym) (locQuery loc) of
Nothing -> routeLoc loc (f False)
Just Nothing -> routeLoc loc (f True)
Just (Just _) -> Left Fail
RPath sym a -> case locPath loc of
[] -> Left Fail
p:paths -> if p == T.pack (symbolVal sym)
then routeLoc (loc { locPath = paths }) a
else Left Fail
RPage a ->
case locPath loc of
[] -> Right a
[""] -> Right a
_ -> Left Fail
uriToLocation :: URI -> Location
uriToLocation uri = Location
{ locPath = decodePathSegments $ BS.pack (uriPath uri)
, locQuery = parseQuery $ BS.pack (uriQuery uri)
}