Merge pull request #254 from zerobuzz/fmap-router

fmapRouter.
This commit is contained in:
Julian Arni 2015-10-13 09:37:28 +02:00
commit 0cf4a5407e
3 changed files with 52 additions and 16 deletions

View file

@ -33,7 +33,7 @@ module Servant.Server
, embedNat , embedNat
, squashNat , squashNat
, generalizeNat , generalizeNat
, tweakResponse
-- * Default error type -- * Default error type
, ServantErr(..) , ServantErr(..)

View file

@ -1,25 +1,34 @@
{-# LANGUAGE DeriveFunctor #-}
module Servant.Server.Internal.Router where module Servant.Server.Internal.Router where
import Data.Map (Map) import Data.Map (Map)
import qualified Data.Map as M import qualified Data.Map as M
import Data.Monoid ((<>)) import Data.Monoid ((<>))
import Data.Text (Text) import Data.Text (Text)
import Network.Wai (Request, pathInfo) import Network.Wai (Request, Response, pathInfo)
import Servant.Server.Internal.PathInfo import Servant.Server.Internal.PathInfo
import Servant.Server.Internal.RoutingApplication import Servant.Server.Internal.RoutingApplication
type Router = Router' RoutingApplication
-- | Internal representation of a router. -- | Internal representation of a router.
data Router = data Router' a =
WithRequest (Request -> Router) WithRequest (Request -> Router)
-- ^ current request is passed to the router -- ^ current request is passed to the router
| StaticRouter (Map Text Router) | StaticRouter (Map Text Router)
-- ^ first path component used for lookup and removed afterwards -- ^ first path component used for lookup and removed afterwards
| DynamicRouter (Text -> Router) | DynamicRouter (Text -> Router)
-- ^ first path component used for lookup and removed afterwards -- ^ first path component used for lookup and removed afterwards
| LeafRouter RoutingApplication | LeafRouter a
-- ^ to be used for routes that match an empty path -- ^ to be used for routes that match an empty path
| Choice Router Router | Choice Router Router
-- ^ left-biased choice between two routers -- ^ left-biased choice between two routers
deriving Functor
-- | Apply a transformation to the response of a `Router`.
tweakResponse :: (RouteResult Response -> RouteResult Response) -> Router -> Router
tweakResponse f = fmap (\a -> \req cont -> a req (cont . f))
-- | Smart constructor for the choice between routers. -- | Smart constructor for the choice between routers.
-- We currently optimize the following cases: -- We currently optimize the following cases:
@ -69,4 +78,3 @@ runRouter (Choice r1 r2) request respond =
then runRouter r2 request $ \ mResponse2 -> then runRouter r2 request $ \ mResponse2 ->
respond (mResponse1 <> mResponse2) respond (mResponse1 <> mResponse2)
else respond mResponse1 else respond mResponse1

View file

@ -1,14 +1,17 @@
{-# LANGUAGE DataKinds #-} {-# LANGUAGE CPP #-}
{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DataKinds #-}
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeOperators #-} {-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeSynonymInstances #-} {-# LANGUAGE TypeOperators #-}
{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE FlexibleInstances #-}
module Servant.ServerSpec where module Servant.ServerSpec where
#if !MIN_VERSION_base(4,8,0)
import Control.Applicative ((<$>))
#endif
import Control.Monad (forM_, when) import Control.Monad (forM_, when)
import Control.Monad.Trans.Except (ExceptT, throwE) import Control.Monad.Trans.Except (ExceptT, throwE)
import Data.Aeson (FromJSON, ToJSON, decode', encode) import Data.Aeson (FromJSON, ToJSON, decode', encode)
@ -23,10 +26,12 @@ import GHC.Generics (Generic)
import Network.HTTP.Types (hAccept, hContentType, import Network.HTTP.Types (hAccept, hContentType,
methodDelete, methodGet, methodHead, methodDelete, methodGet, methodHead,
methodPatch, methodPost, methodPut, methodPatch, methodPost, methodPut,
ok200, parseQuery, status409) ok200, parseQuery, status409,
Status(..))
import Network.Wai (Application, Request, pathInfo, import Network.Wai (Application, Request, pathInfo,
queryString, rawQueryString, queryString, rawQueryString,
responseLBS) responseLBS, responseBuilder)
import Network.Wai.Internal (Response(ResponseBuilder))
import Network.Wai.Test (defaultRequest, request, import Network.Wai.Test (defaultRequest, request,
runSession, simpleBody) runSession, simpleBody)
import Test.Hspec (Spec, describe, it, shouldBe) import Test.Hspec (Spec, describe, it, shouldBe)
@ -41,8 +46,12 @@ import Servant.API ((:<|>) (..), (:>),
Post, Put, RemoteHost, QueryFlag, QueryParam, Post, Put, RemoteHost, QueryFlag, QueryParam,
QueryParams, Raw, ReqBody) QueryParams, Raw, ReqBody)
import Servant.Server (Server, serve, ServantErr(..), err404) import Servant.Server (Server, serve, ServantErr(..), err404)
import Servant.Server.Internal.Router
(tweakResponse, runRouter,
Router, Router'(LeafRouter))
import Servant.Server.Internal.RoutingApplication import Servant.Server.Internal.RoutingApplication
(RouteMismatch (..)) (RouteResult(..), RouteMismatch(..),
toApplication)
-- * test data types -- * test data types
@ -92,6 +101,7 @@ spec = do
unionSpec unionSpec
prioErrorsSpec prioErrorsSpec
errorsSpec errorsSpec
routerSpec
responseHeadersSpec responseHeadersSpec
miscReqCombinatorsSpec miscReqCombinatorsSpec
@ -697,6 +707,24 @@ errorsSpec = do
nf <> ib `shouldBe` ib nf <> ib `shouldBe` ib
nf <> wm `shouldBe` wm nf <> wm `shouldBe` wm
routerSpec :: Spec
routerSpec = do
describe "Servant.Server.Internal.Router" $ do
let app' :: Application
app' = toApplication $ runRouter router'
router', router :: Router
router' = tweakResponse (twk <$>) router
router = LeafRouter $ \_ cont -> cont (RR . Right $ responseBuilder (Status 201 "") [] "")
twk :: Response -> Response
twk (ResponseBuilder (Status i s) hs b) = ResponseBuilder (Status (i + 1) s) hs b
twk b = b
describe "tweakResponse" . with (return app') $ do
it "calls f on route result" $ do
get "" `shouldRespondWith` 202
type MiscCombinatorsAPI type MiscCombinatorsAPI
= "version" :> HttpVersion :> Get '[JSON] String = "version" :> HttpVersion :> Get '[JSON] String
:<|> "secure" :> IsSecure :> Get '[JSON] String :<|> "secure" :> IsSecure :> Get '[JSON] String