{-# LANGUAGE ExistentialQuantification #-}
module Ecluse.Core.Server.Route (
Route (..),
RouteName (..),
PatternSeg (..),
Capture (..),
MethodMatch (..),
MediaNegotiation (..),
routerOf,
matchRoute,
renderRoute,
answering,
refusing,
safeSegment,
isHead,
) where
import Network.HTTP.Types.Header (RequestHeaders)
import Network.HTTP.Types.Method (Method, methodDelete, methodGet, methodHead, methodPost, methodPut)
import Ecluse.Core.Server.Accept (acceptsAny)
import Ecluse.Core.Server.Context (
MountRouter,
ResponseAction (AnswerLocally, AnswerRefusal),
RouteAction (RouteAction),
)
import Ecluse.Core.Server.Contract (RequestSpec, ResponseContract, bodilessContract)
import Ecluse.Core.Server.Path (isSafeComponent)
import Ecluse.Core.Server.Response (HelpMessage)
data Route v = forall response. Route
{ forall v. Route v -> RouteName
routeName :: RouteName
, forall v. Route v -> MethodMatch
routeMethod :: MethodMatch
, ()
routeAccepts :: MediaNegotiation response
, 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])
, forall v. Capture v -> v -> [Text]
capRender :: v -> [Text]
}
data MediaNegotiation response
=
AcceptsAnything
|
AcceptsOnly (NonEmpty ByteString) (Maybe HelpMessage -> response)
data MethodMatch
=
MethodPut
|
MethodPost
|
MethodDelete
|
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
MethodPost Method
m = Method
m Method -> Method -> Bool
forall a. Eq a => a -> a -> Bool
== Method
methodPost
methodMatches MethodMatch
MethodDelete Method
m = Method
m Method -> Method -> Bool
forall a. Eq a => a -> a -> Bool
== Method
methodDelete
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 RequestHeaders
headers [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
fallbackAction Method
method RouteAction
notFound) (Route v, RouteAction) -> RouteAction
forall a b. (a, b) -> b
snd ([Route v]
-> Method
-> RequestHeaders
-> [Text]
-> Maybe (Route v, RouteAction)
forall v.
[Route v]
-> Method
-> RequestHeaders
-> [Text]
-> Maybe (Route v, RouteAction)
matchRoute [Route v]
routes Method
method RequestHeaders
headers [Text]
segments)
fallbackAction :: Method -> RouteAction -> RouteAction
fallbackAction :: Method -> RouteAction -> RouteAction
fallbackAction Method
method (RouteAction ResponseContract response
contract ResponseAction response
action) =
ResponseContract response -> ResponseAction response -> RouteAction
forall response.
ResponseContract response -> ResponseAction response -> RouteAction
RouteAction (Method -> ResponseContract response -> ResponseContract response
forall response.
Method -> ResponseContract response -> ResponseContract response
contractForMethod Method
method ResponseContract response
contract) ResponseAction response
action
matchRoute :: [Route v] -> Method -> RequestHeaders -> [Text] -> Maybe (Route v, RouteAction)
matchRoute :: forall v.
[Route v]
-> Method
-> RequestHeaders
-> [Text]
-> Maybe (Route v, RouteAction)
matchRoute [Route v]
routes Method
method RequestHeaders
headers [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, routeAccepts :: ()
routeAccepts = MediaNegotiation response
negotiation, 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 <- negotiated headers negotiation (build method captures)
pure (route, RouteAction (contractForMethod method contract) action)
| Bool
otherwise = Maybe (Route v, RouteAction)
forall a. Maybe a
Nothing
contractForMethod :: Method -> ResponseContract response -> ResponseContract response
contractForMethod :: forall response.
Method -> ResponseContract response -> ResponseContract response
contractForMethod Method
method
| Method -> Bool
isHead Method
method = ResponseContract response -> ResponseContract response
forall response.
ResponseContract response -> ResponseContract response
bodilessContract
| Bool
otherwise = ResponseContract response -> ResponseContract response
forall a. a -> a
id
negotiated ::
RequestHeaders ->
MediaNegotiation response ->
Maybe (ResponseAction response) ->
Maybe (ResponseAction response)
negotiated :: forall response.
RequestHeaders
-> MediaNegotiation response
-> Maybe (ResponseAction response)
-> Maybe (ResponseAction response)
negotiated RequestHeaders
headers MediaNegotiation response
negotiation Maybe (ResponseAction response)
built = case MediaNegotiation response
negotiation of
MediaNegotiation response
AcceptsAnything -> Maybe (ResponseAction response)
built
AcceptsOnly NonEmpty Method
served Maybe HelpMessage -> response
refusal
| RequestHeaders -> NonEmpty Method -> Bool
acceptsAny RequestHeaders
headers NonEmpty Method
served -> Maybe (ResponseAction response)
built
| Bool
otherwise -> (Maybe HelpMessage -> response) -> ResponseAction response
forall response.
(Maybe HelpMessage -> response) -> ResponseAction response
AnswerRefusal Maybe HelpMessage -> response
refusal ResponseAction response
-> Maybe (ResponseAction response)
-> Maybe (ResponseAction response)
forall a b. a -> Maybe b -> Maybe a
forall (f :: * -> *) a b. Functor f => a -> f b -> f a
<$ Maybe (ResponseAction response)
built
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
answering :: response -> Method -> [v] -> Maybe (ResponseAction response)
answering :: forall response v.
response -> Method -> [v] -> Maybe (ResponseAction response)
answering response
answer Method
_method [v]
_captures = ResponseAction response -> Maybe (ResponseAction response)
forall a. a -> Maybe a
Just (response -> ResponseAction response
forall response. response -> ResponseAction response
AnswerLocally response
answer)
refusing :: (Maybe HelpMessage -> response) -> Method -> [v] -> Maybe (ResponseAction response)
refusing :: forall response v.
(Maybe HelpMessage -> response)
-> Method -> [v] -> Maybe (ResponseAction response)
refusing Maybe HelpMessage -> response
render Method
_method [v]
_captures = ResponseAction response -> Maybe (ResponseAction response)
forall a. a -> Maybe a
Just ((Maybe HelpMessage -> response) -> ResponseAction response
forall response.
(Maybe HelpMessage -> response) -> ResponseAction response
AnswerRefusal Maybe HelpMessage -> response
render)
safeSegment :: (Text -> v) -> [Text] -> Maybe (v, [Text])
safeSegment :: forall v. (Text -> v) -> [Text] -> Maybe (v, [Text])
safeSegment Text -> v
build = \case
Text
seg : [Text]
rest | Text -> Bool
isSafeComponent Text
seg -> (v, [Text]) -> Maybe (v, [Text])
forall a. a -> Maybe a
Just (Text -> v
build Text
seg, [Text]
rest)
[Text]
_ -> Maybe (v, [Text])
forall a. Maybe a
Nothing
renderRoute :: Route v -> [v] -> Maybe [Text]
renderRoute :: forall v. Route v -> [v] -> Maybe [Text]
renderRoute Route{routeSegs :: forall v. Route v -> [PatternSeg v]
routeSegs = [PatternSeg v]
patternSegs} = [PatternSeg v] -> [v] -> Maybe [Text]
forall v. [PatternSeg v] -> [v] -> Maybe [Text]
fillSegs [PatternSeg v]
patternSegs
fillSegs :: [PatternSeg v] -> [v] -> Maybe [Text]
fillSegs :: forall v. [PatternSeg v] -> [v] -> Maybe [Text]
fillSegs [] [] = [Text] -> Maybe [Text]
forall a. a -> Maybe a
Just []
fillSegs (SegLit Text
lit : [PatternSeg v]
ps) [v]
vs = (Text
lit Text -> [Text] -> [Text]
forall a. a -> [a] -> [a]
:) ([Text] -> [Text]) -> Maybe [Text] -> Maybe [Text]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> [PatternSeg v] -> [v] -> Maybe [Text]
forall v. [PatternSeg v] -> [v] -> Maybe [Text]
fillSegs [PatternSeg v]
ps [v]
vs
fillSegs (SegCap Capture v
capture : [PatternSeg v]
ps) (v
v : [v]
vs) = (Capture v -> v -> [Text]
forall v. Capture v -> v -> [Text]
capRender Capture v
capture v
v [Text] -> [Text] -> [Text]
forall a. Semigroup a => a -> a -> a
<>) ([Text] -> [Text]) -> Maybe [Text] -> Maybe [Text]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> [PatternSeg v] -> [v] -> Maybe [Text]
forall v. [PatternSeg v] -> [v] -> Maybe [Text]
fillSegs [PatternSeg v]
ps [v]
vs
fillSegs [PatternSeg v]
_ [v]
_ = Maybe [Text]
forall a. Maybe a
Nothing
isHead :: Method -> Bool
isHead :: Method -> Bool
isHead = (Method -> Method -> Bool
forall a. Eq a => a -> a -> Bool
== Method
methodHead)