1 {-# LANGUAGE GeneralizedNewtypeDeriving #-}
2 {-# LANGUAGE OverloadedStrings #-}
3 {-# LANGUAGE StrictData #-}
4 {-# LANGUAGE TypeFamilies #-}
5 {-# OPTIONS_GHC -fno-warn-orphans #-}
6 module Language.Symantic.HTTP.Router where
8 import Control.Applicative (Applicative(..), Alternative(..))
9 import Control.Monad (Monad(..), MonadPlus(..), void)
11 import Data.Either (Either(..))
12 import Data.Eq (Eq(..))
13 import Data.Foldable (toList)
14 import Data.Function (($), (.), id)
15 import Data.Functor (Functor)
16 import Data.Maybe (Maybe(..))
17 import Data.Ord (Ord(..))
18 import Data.Semigroup (Semigroup(..))
19 import Data.String (IsString(..))
20 import Data.Tuple (fst, snd)
21 import Prelude (Num(..), max, undefined)
23 import Text.Show (Show(..))
24 import qualified Control.Monad.Trans.Reader as R
25 import qualified Data.ByteString as BS
26 import qualified Data.List as List
27 import qualified Data.Set as Set
28 import qualified Data.Text as Text
29 import qualified Network.HTTP.Media as Media
30 import qualified Network.HTTP.Types as HTTP
31 import qualified Network.Wai as Wai
32 import qualified Text.Megaparsec as P
34 import Language.Symantic.HTTP.Media
35 import Language.Symantic.HTTP.API
38 newtype Router a = Router { unRouter :: R.ReaderT Wai.Request (P.Parsec RouteError RouteTokens) a }
39 deriving (Functor, Applicative, Alternative, Monad, MonadPlus, P.MonadParsec RouteError RouteTokens)
41 runRouter :: Router a -> Wai.Request -> RoutingResult a
42 runRouter (Router rt) rq =
43 let p = R.runReaderT rt rq in
44 P.runParser (p <* P.eof) "<Request>" $
45 RouteToken_Segment <$> Wai.pathInfo rq
47 runRouterApp :: Router Application -> Wai.Application
48 runRouterApp rt rq re =
49 case runRouter rt rq of
50 Right app -> runApplication app rq re
51 Left err -> re $ Wai.responseLBS
52 (HTTP.mkStatus 404 "Not Found")
53 [(HTTP.hContentType, Media.renderHeader $ mediaType plainText)]
54 (fromString $ P.errorBundlePretty err)
57 runRouterIO :: Show a => Router (IO a) -> Wai.Request -> IO ()
59 case runRouter rt rq of
60 Left err -> putStrLn $ P.parseErrorPretty err
61 Right a -> print =<< a
64 -- ** Type 'RouteError'
66 = RouteError_Accept_unsupported {-expected-}MediaType {-got-}HeaderValue
67 | RouteError_Query_param_not_a_boolean QueryName [Maybe BS.ByteString]
68 deriving (Eq, Ord, Show)
69 instance P.ShowErrorComponent RouteError where
70 showErrorComponent = show
72 -- ** Type 'RoutingResult'
73 type RoutingResult = Either RoutingError
74 type RoutingError = P.ParseErrorBundle RouteTokens RouteError
76 -- * Type 'Application'
77 newtype Application = Application
79 (RoutingResult Wai.Response -> IO Wai.ResponseReceived) ->
80 IO Wai.ResponseReceived)
82 runApplication :: Application -> Wai.Application
83 runApplication (Application app) rq re = app rq routingRespond
85 routingRespond :: RoutingResult Wai.Response -> IO Wai.ResponseReceived
86 routingRespond = \case
88 Left err -> re $ Wai.responseLBS
89 (HTTP.mkStatus 404 "Not Found")
90 [(HTTP.hContentType, Media.renderHeader $ mediaType plainText)]
91 (fromString $ P.errorBundlePretty err)
93 -- * Type 'RouteTokens'
94 type RouteTokens = [RouteToken]
95 instance P.Stream RouteTokens where
96 type Token RouteTokens = RouteToken
97 type Tokens RouteTokens = RouteTokens
99 takeN_ n s | n <= 0 = Just ([], s)
100 | List.null s = Nothing
101 | otherwise = Just (List.splitAt n s)
102 takeWhile_ = List.span
103 tokenToChunk _ps = pure
104 tokensToChunk _ps = id
105 chunkToTokens _ps = id
106 chunkLength _ps = List.length
107 chunkEmpty _ps = List.null
108 showTokens _s toks = List.intercalate ", " $ toList $ show <$> toks
109 reachOffset o pos@P.PosState{..} =
111 , List.head $ (show <$> inp)<>["End"]
113 { P.pstateInput = inp
114 , P.pstateOffset = max o pstateOffset
115 , P.pstateSourcePos = spos
119 inp = List.drop d pstateInput
120 line | d == 0 = P.sourceLine pstateSourcePos
121 | otherwise = P.sourceLine pstateSourcePos <> P.mkPos d
122 spos = pstateSourcePos{P.sourceLine = line}
123 instance P.Stream Path where
124 type Token Path = Segment
125 type Tokens Path = [Segment]
127 takeN_ n s | n <= 0 = Just ([], s)
128 | List.null s = Nothing
129 | otherwise = Just (List.splitAt n s)
130 takeWhile_ = List.span
131 tokenToChunk _ps = pure
132 tokensToChunk _ps = id
133 chunkToTokens _ps = id
134 chunkLength _ps = List.length
135 chunkEmpty _ps = List.null
136 showTokens _s toks = List.intercalate ", " $ toList $ Text.unpack <$> toks
137 reachOffset o pos@P.PosState{..} =
139 , List.head $ (show <$> inp)<>["End"]
141 { P.pstateInput = inp
142 , P.pstateOffset = max o pstateOffset
143 , P.pstateSourcePos = spos
148 inp = List.drop d pstateInput
149 spos = pstateSourcePos{P.sourceLine = P.sourceLine pstateSourcePos <> P.mkPos d}
151 -- ** Type 'RouteToken'
153 = RouteToken_Segment Segment
154 | RouteToken_Header HTTP.HeaderName
155 | RouteToken_Headers HTTP.RequestHeaders
156 | RouteToken_Query QueryName
157 | RouteToken_QueryString HTTP.Query
158 | RouteToken_Method HTTP.Method
159 | RouteToken_Version HTTP.HttpVersion
160 deriving (Eq, Ord, Show)
162 unRouteToken_Segment :: RouteToken -> Segment
163 unRouteToken_Segment (RouteToken_Segment x) = x
164 unRouteToken_Segment _ = undefined
166 instance Altern Router where
168 x <+> y = P.try x <|> y
170 instance HTTP_Path Router where
171 segment = void . P.single . RouteToken_Segment
172 capture _n = unRouteToken_Segment <$> P.anySingle
173 captureAll = P.many $ unRouteToken_Segment <$> P.anySingle
174 instance HTTP_Method Router where
176 got <- Router $ R.asks Wai.requestMethod
178 P.setInput [RouteToken_Method got]
179 ret <- (`P.token` Set.singleton (P.Tokens $ pure $ RouteToken_Method exp)) $ \_tok ->
185 instance HTTP_Header Router where
187 got <- Router $ R.asks Wai.requestHeaders
189 P.setInput [RouteToken_Headers got]
190 ret <- (`P.token` Set.singleton (P.Tokens $ pure $ RouteToken_Header exp)) $ \_tok ->
194 instance HTTP_Accept Router where
196 h <- header HTTP.hAccept
197 case Media.parseAccept h of
198 Just got | mediaType exp`Media.matches`got -> return $ toMediaType exp
199 _ -> P.fancyFailure $ Set.singleton $
200 P.ErrorCustom $ RouteError_Accept_unsupported (mediaType exp) h
201 instance HTTP_Query Router where
203 got <- Router $ R.asks Wai.queryString
205 P.setInput [RouteToken_QueryString got]
206 ret <- (`P.token` Set.singleton (P.Tokens $ pure $ RouteToken_Query exp)) $ \_tok ->
207 case List.filter ((== exp) . fst) got of
209 hs -> Just $ snd <$> hs
216 [Nothing] -> return True
217 [Just "0"] -> return False
218 [Just "false"] -> return False
219 [Just "1"] -> return True
220 [Just "true"] -> return True
221 _ -> P.fancyFailure $ Set.singleton $
222 P.ErrorCustom $ RouteError_Query_param_not_a_boolean n vs
223 instance HTTP_Version Router where
225 got <- Router $ R.asks Wai.httpVersion
227 P.setInput [RouteToken_Version got]
228 ret <- (`P.token` Set.singleton (P.Tokens $ pure $ RouteToken_Version exp)) $ \_tok ->
234 -- ** Type 'RouterEndpoint'
235 newtype RouterEndpoint a
236 = RouterEndpoint (HTTP.Status -> HTTP.ResponseHeaders -> a -> Wai.Response)
237 instance HTTP_Endpoint Router where
238 type Endpoint Router = RouterEndpoint
239 endpoint expMethod expAccept = do
240 meth <- if expMethod == HTTP.methodGet
241 then method HTTP.methodHead <+> method HTTP.methodGet
242 else method expMethod
243 hAccept <- header HTTP.hAccept
244 let mt = mediaType expAccept
245 case Media.parseAccept hAccept of
246 Just gotAccept | mediaType expAccept`Media.matches`gotAccept ->
247 return $ RouterEndpoint $ \st hs a ->
249 ((HTTP.hContentType, Media.renderHeader mt):hs)
250 (if meth == HTTP.methodHead then "" else toMediaType expAccept a)
251 _ -> P.fancyFailure $ Set.singleton $
252 P.ErrorCustom $ RouteError_Accept_unsupported mt hAccept
253 instance HTTP_API Router