{-# LANGUAGE ExistentialQuantification #-}
module Ecluse.Core.Server.Route (
Route (..),
RouteName (..),
PatternSeg (..),
Capture (..),
MethodMatch (..),
routerOf,
matchRoute,
) where
import Network.HTTP.Types.Method (Method, methodGet, methodHead, methodPut)
import Ecluse.Core.Server.Context (MountRouter, ResponseAction, RouteAction (RouteAction))
import Ecluse.Core.Server.Contract (RequestSpec, ResponseContract, bodilessContract)
data Route v = forall response. Route
{ forall v. Route v -> RouteName
routeName :: RouteName
, forall v. Route v -> MethodMatch
routeMethod :: MethodMatch
, forall v. Route v -> [PatternSeg v]
routeSegs :: [PatternSeg v]
, ()
routeBuild :: Method -> [v] -> Maybe (ResponseAction response)
, forall v. Route v -> Text
routeSummary :: Text
, forall v. Route v -> Text
routeDescription :: Text
, forall v. Route v -> Maybe RequestSpec
routeRequest :: Maybe RequestSpec
, ()
routeContract :: ResponseContract response
}
newtype RouteName = RouteName {RouteName -> Text
unRouteName :: Text}
deriving stock (RouteName -> RouteName -> Bool
(RouteName -> RouteName -> Bool)
-> (RouteName -> RouteName -> Bool) -> Eq RouteName
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: RouteName -> RouteName -> Bool
== :: RouteName -> RouteName -> Bool
$c/= :: RouteName -> RouteName -> Bool
/= :: RouteName -> RouteName -> Bool
Eq, Eq RouteName
Eq RouteName =>
(RouteName -> RouteName -> Ordering)
-> (RouteName -> RouteName -> Bool)
-> (RouteName -> RouteName -> Bool)
-> (RouteName -> RouteName -> Bool)
-> (RouteName -> RouteName -> Bool)
-> (RouteName -> RouteName -> RouteName)
-> (RouteName -> RouteName -> RouteName)
-> Ord RouteName
RouteName -> RouteName -> Bool
RouteName -> RouteName -> Ordering
RouteName -> RouteName -> RouteName
forall a.
Eq a =>
(a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
$ccompare :: RouteName -> RouteName -> Ordering
compare :: RouteName -> RouteName -> Ordering
$c< :: RouteName -> RouteName -> Bool
< :: RouteName -> RouteName -> Bool
$c<= :: RouteName -> RouteName -> Bool
<= :: RouteName -> RouteName -> Bool
$c> :: RouteName -> RouteName -> Bool
> :: RouteName -> RouteName -> Bool
$c>= :: RouteName -> RouteName -> Bool
>= :: RouteName -> RouteName -> Bool
$cmax :: RouteName -> RouteName -> RouteName
max :: RouteName -> RouteName -> RouteName
$cmin :: RouteName -> RouteName -> RouteName
min :: RouteName -> RouteName -> RouteName
Ord, Int -> RouteName -> ShowS
[RouteName] -> ShowS
RouteName -> String
(Int -> RouteName -> ShowS)
-> (RouteName -> String)
-> ([RouteName] -> ShowS)
-> Show RouteName
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> RouteName -> ShowS
showsPrec :: Int -> RouteName -> ShowS
$cshow :: RouteName -> String
show :: RouteName -> String
$cshowList :: [RouteName] -> ShowS
showList :: [RouteName] -> ShowS
Show)
data PatternSeg v
= SegLit Text
| SegCap (Capture v)
data Capture v = Capture
{ forall v. Capture v -> Text
capName :: Text
, forall v. Capture v -> Text
capDescription :: Text
, forall v. Capture v -> [Text] -> Maybe (v, [Text])
capConsume :: [Text] -> Maybe (v, [Text])
}
data MethodMatch
=
MethodPut
|
MethodRead
deriving stock (MethodMatch -> MethodMatch -> Bool
(MethodMatch -> MethodMatch -> Bool)
-> (MethodMatch -> MethodMatch -> Bool) -> Eq MethodMatch
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: MethodMatch -> MethodMatch -> Bool
== :: MethodMatch -> MethodMatch -> Bool
$c/= :: MethodMatch -> MethodMatch -> Bool
/= :: MethodMatch -> MethodMatch -> Bool
Eq, Int -> MethodMatch -> ShowS
[MethodMatch] -> ShowS
MethodMatch -> String
(Int -> MethodMatch -> ShowS)
-> (MethodMatch -> String)
-> ([MethodMatch] -> ShowS)
-> Show MethodMatch
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> MethodMatch -> ShowS
showsPrec :: Int -> MethodMatch -> ShowS
$cshow :: MethodMatch -> String
show :: MethodMatch -> String
$cshowList :: [MethodMatch] -> ShowS
showList :: [MethodMatch] -> ShowS
Show)
methodMatches :: MethodMatch -> Method -> Bool
methodMatches :: MethodMatch -> Method -> Bool
methodMatches MethodMatch
MethodPut Method
m = Method
m Method -> Method -> Bool
forall a. Eq a => a -> a -> Bool
== Method
methodPut
methodMatches MethodMatch
MethodRead Method
m = Method
m Method -> Method -> Bool
forall a. Eq a => a -> a -> Bool
== Method
methodGet Bool -> Bool -> Bool
|| Method
m Method -> Method -> Bool
forall a. Eq a => a -> a -> Bool
== Method
methodHead
routerOf :: RouteAction -> [Route v] -> MountRouter
routerOf :: forall v. RouteAction -> [Route v] -> MountRouter
routerOf RouteAction
notFound [Route v]
routes Method
method [Text]
segments =
RouteAction
-> ((Route v, RouteAction) -> RouteAction)
-> Maybe (Route v, RouteAction)
-> RouteAction
forall b a. b -> (a -> b) -> Maybe a -> b
maybe (Method -> RouteAction -> RouteAction
fallbackFor Method
method RouteAction
notFound) (Route v, RouteAction) -> RouteAction
forall a b. (a, b) -> b
snd ([Route v] -> Method -> [Text] -> Maybe (Route v, RouteAction)
forall v.
[Route v] -> Method -> [Text] -> Maybe (Route v, RouteAction)
matchRoute [Route v]
routes Method
method [Text]
segments)
where
fallbackFor :: Method -> RouteAction -> RouteAction
fallbackFor Method
requested (RouteAction ResponseContract response
contract ResponseAction response
action)
| Method
requested Method -> Method -> Bool
forall a. Eq a => a -> a -> Bool
== Method
methodHead = ResponseContract response -> ResponseAction response -> RouteAction
forall response.
ResponseContract response -> ResponseAction response -> RouteAction
RouteAction (ResponseContract response -> ResponseContract response
forall response.
ResponseContract response -> ResponseContract response
bodilessContract ResponseContract response
contract) ResponseAction response
action
| Bool
otherwise = ResponseContract response -> ResponseAction response -> RouteAction
forall response.
ResponseContract response -> ResponseAction response -> RouteAction
RouteAction ResponseContract response
contract ResponseAction response
action
matchRoute :: [Route v] -> Method -> [Text] -> Maybe (Route v, RouteAction)
matchRoute :: forall v.
[Route v] -> Method -> [Text] -> Maybe (Route v, RouteAction)
matchRoute [Route v]
routes Method
method [Text]
segments =
[(Route v, RouteAction)] -> Maybe (Route v, RouteAction)
forall a. [a] -> Maybe a
listToMaybe ((Route v -> Maybe (Route v, RouteAction))
-> [Route v] -> [(Route v, RouteAction)]
forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe Route v -> Maybe (Route v, RouteAction)
forall {v}. Route v -> Maybe (Route v, RouteAction)
claim [Route v]
routes)
where
claim :: Route v -> Maybe (Route v, RouteAction)
claim route :: Route v
route@Route{routeMethod :: forall v. Route v -> MethodMatch
routeMethod = MethodMatch
matchedMethod, routeSegs :: forall v. Route v -> [PatternSeg v]
routeSegs = [PatternSeg v]
patternSegs, routeBuild :: ()
routeBuild = Method -> [v] -> Maybe (ResponseAction response)
build, routeContract :: ()
routeContract = ResponseContract response
contract}
| MethodMatch -> Method -> Bool
methodMatches MethodMatch
matchedMethod Method
method = do
captures <- [PatternSeg v] -> [Text] -> Maybe [v]
forall v. [PatternSeg v] -> [Text] -> Maybe [v]
consumeSegs [PatternSeg v]
patternSegs [Text]
segments
action <- build method captures
pure (route, RouteAction (contractFor method contract) action)
| Bool
otherwise = Maybe (Route v, RouteAction)
forall a. Maybe a
Nothing
contractFor :: Method -> ResponseContract response -> ResponseContract response
contractFor Method
requested
| Method
requested Method -> Method -> Bool
forall a. Eq a => a -> a -> Bool
== Method
methodHead = ResponseContract response -> ResponseContract response
forall response.
ResponseContract response -> ResponseContract response
bodilessContract
| Bool
otherwise = ResponseContract response -> ResponseContract response
forall a. a -> a
id
consumeSegs :: [PatternSeg v] -> [Text] -> Maybe [v]
consumeSegs :: forall v. [PatternSeg v] -> [Text] -> Maybe [v]
consumeSegs [] [] = [v] -> Maybe [v]
forall a. a -> Maybe a
Just []
consumeSegs (SegLit Text
l : [PatternSeg v]
ps) (Text
s : [Text]
ss)
| Text
l Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
s = [PatternSeg v] -> [Text] -> Maybe [v]
forall v. [PatternSeg v] -> [Text] -> Maybe [v]
consumeSegs [PatternSeg v]
ps [Text]
ss
consumeSegs (SegCap Capture v
c : [PatternSeg v]
ps) [Text]
ss = do
(v, rest) <- Capture v -> [Text] -> Maybe (v, [Text])
forall v. Capture v -> [Text] -> Maybe (v, [Text])
capConsume Capture v
c [Text]
ss
(v :) <$> consumeSegs ps rest
consumeSegs [PatternSeg v]
_ [Text]
_ = Maybe [v]
forall a. Maybe a
Nothing