added Servant.API.Raw (only with HasServer)

This commit is contained in:
Sönke Hahn 2014-10-27 18:24:20 +08:00
parent 207f398572
commit b749f7fbc6
11 changed files with 87 additions and 33 deletions

View file

@ -23,6 +23,7 @@ library
Servant.API.Get Servant.API.Get
Servant.API.GetParam Servant.API.GetParam
Servant.API.Post Servant.API.Post
Servant.API.Raw
Servant.API.RQBody Servant.API.RQBody
Servant.API.Sub Servant.API.Sub
Servant.API.Union Servant.API.Union

View file

@ -26,14 +26,14 @@ instance (KnownSymbol capture, FromText a, HasServer sublayout)
type Server (Capture capture a :> sublayout) = type Server (Capture capture a :> sublayout) =
a -> Server sublayout a -> Server sublayout
route Proxy subserver request = case pathInfo request of route Proxy subserver globalPathInfo request respond = case pathInfo request of
(first : rest) (first : rest)
-> case captured captureProxy first of -> case captured captureProxy first of
Nothing -> return Nothing Nothing -> respond Nothing
Just v -> route (Proxy :: Proxy sublayout) (subserver v) request{ Just v -> route (Proxy :: Proxy sublayout) (subserver v) globalPathInfo request{
pathInfo = rest pathInfo = rest
} } respond
_ -> return Nothing _ -> respond Nothing
where captureProxy = Proxy :: Proxy (Capture capture a) where captureProxy = Proxy :: Proxy (Capture capture a)

View file

@ -22,15 +22,15 @@ data Get a
instance ToJSON result => HasServer (Get result) where instance ToJSON result => HasServer (Get result) where
type Server (Get result) = EitherT (Int, String) IO result type Server (Get result) = EitherT (Int, String) IO result
route Proxy action request route Proxy action _globalPathInfo request respond
| null (pathInfo request) && requestMethod request == methodGet = do | null (pathInfo request) && requestMethod request == methodGet = do
e <- runEitherT action e <- runEitherT action
return $ Just $ case e of respond $ Just $ case e of
Right output -> Right output ->
responseLBS ok200 [("Content-Type", "application/json")] (encode output) responseLBS ok200 [("Content-Type", "application/json")] (encode output)
Left (status, message) -> Left (status, message) ->
responseLBS (mkStatus status (cs message)) [] (cs message) responseLBS (mkStatus status (cs message)) [] (cs message)
| otherwise = return Nothing | otherwise = respond Nothing
instance FromJSON result => HasClient (Get result) where instance FromJSON result => HasClient (Get result) where
type Client (Get result) = URI -> EitherT String IO result type Client (Get result) = URI -> EitherT String IO result

View file

@ -25,7 +25,7 @@ instance (KnownSymbol sym, FromText a, HasServer sublayout)
type Server (GetParam sym a :> sublayout) = type Server (GetParam sym a :> sublayout) =
Maybe a -> Server sublayout Maybe a -> Server sublayout
route Proxy subserver request = do route Proxy subserver globalPathInfo request respond = do
let querytext = parseQueryText $ rawQueryString request let querytext = parseQueryText $ rawQueryString request
param = param =
case lookup paramName querytext of case lookup paramName querytext of
@ -34,7 +34,7 @@ instance (KnownSymbol sym, FromText a, HasServer sublayout)
Just (Just v) -> fromText v -- if present, we try to convert to Just (Just v) -> fromText v -- if present, we try to convert to
-- the right type -- the right type
route (Proxy :: Proxy sublayout) (subserver param) request route (Proxy :: Proxy sublayout) (subserver param) globalPathInfo request respond
where paramName = cs $ symbolVal (Proxy :: Proxy sym) where paramName = cs $ symbolVal (Proxy :: Proxy sym)

View file

@ -22,15 +22,15 @@ data Post a
instance ToJSON a => HasServer (Post a) where instance ToJSON a => HasServer (Post a) where
type Server (Post a) = EitherT (Int, String) IO a type Server (Post a) = EitherT (Int, String) IO a
route Proxy action request route Proxy action _globalPathInfo request respond
| null (pathInfo request) && requestMethod request == methodPost = do | null (pathInfo request) && requestMethod request == methodPost = do
e <- runEitherT action e <- runEitherT action
return $ Just $ case e of respond $ Just $ case e of
Right out -> Right out ->
responseLBS status201 [("Content-Type", "application/json")] (encode out) responseLBS status201 [("Content-Type", "application/json")] (encode out)
Left (status, message) -> Left (status, message) ->
responseLBS (mkStatus status (cs message)) [] (cs message) responseLBS (mkStatus status (cs message)) [] (cs message)
| otherwise = return Nothing | otherwise = respond Nothing
instance FromJSON a => HasClient (Post a) where instance FromJSON a => HasClient (Post a) where
type Client (Post a) = URI -> EitherT String IO a type Client (Post a) = URI -> EitherT String IO a

View file

@ -22,11 +22,11 @@ instance (FromJSON a, HasServer sublayout)
type Server (RQBody a :> sublayout) = type Server (RQBody a :> sublayout) =
a -> Server sublayout a -> Server sublayout
route Proxy subserver request = do route Proxy subserver globalPathInfo request respond = do
mrqbody <- decode' <$> lazyRequestBody request mrqbody <- decode' <$> lazyRequestBody request
case mrqbody of case mrqbody of
Nothing -> return Nothing Nothing -> respond Nothing
Just v -> route (Proxy :: Proxy sublayout) (subserver v) request Just v -> route (Proxy :: Proxy sublayout) (subserver v) globalPathInfo request respond
instance (ToJSON a, HasClient sublayout) instance (ToJSON a, HasClient sublayout)
=> HasClient (RQBody a :> sublayout) where => HasClient (RQBody a :> sublayout) where

17
src/Servant/API/Raw.hs Normal file
View file

@ -0,0 +1,17 @@
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE OverloadedStrings #-}
module Servant.API.Raw where
import Data.Proxy
import Network.Wai
import Servant.Server
-- | Endpoint for plugging in your own Wai 'Application's.
-- The given Application will get the original request received by the server
-- (i.e. with unmodified 'pathInfo', etc.)
data Raw
instance HasServer Raw where
type Server Raw = Application
route Proxy rawApplication globalPathInfo request respond =
rawApplication request{pathInfo = globalPathInfo} (respond . Just)

View file

@ -18,13 +18,13 @@ infixr 9 :>
instance (KnownSymbol path, HasServer sublayout) => HasServer (path :> sublayout) where instance (KnownSymbol path, HasServer sublayout) => HasServer (path :> sublayout) where
type Server (path :> sublayout) = Server sublayout type Server (path :> sublayout) = Server sublayout
route Proxy subserver request = case pathInfo request of route Proxy subserver globalPathInfo request respond = case pathInfo request of
(first : rest) (first : rest)
| first == cs (symbolVal proxyPath) | first == cs (symbolVal proxyPath)
-> route (Proxy :: Proxy sublayout) subserver request{ -> route (Proxy :: Proxy sublayout) subserver globalPathInfo request{
pathInfo = rest pathInfo = rest
} } respond
_ -> return Nothing _ -> respond Nothing
where proxyPath = Proxy :: Proxy path where proxyPath = Proxy :: Proxy path

View file

@ -13,14 +13,14 @@ infixr 8 :<|>
instance (HasServer a, HasServer b) => HasServer (a :<|> b) where instance (HasServer a, HasServer b) => HasServer (a :<|> b) where
type Server (a :<|> b) = Server a :<|> Server b type Server (a :<|> b) = Server a :<|> Server b
route Proxy (a :<|> b) request = do route Proxy (a :<|> b) globalPathInfo request respond = do
m <- route (Proxy :: Proxy a) a request route (Proxy :: Proxy a) a globalPathInfo request $ \ mResponse ->
case m of case mResponse of
Nothing -> route (Proxy :: Proxy b) b request Nothing -> route (Proxy :: Proxy b) b globalPathInfo request respond
Just response -> return $ Just response Just response -> respond $ Just response
instance (HasClient a, HasClient b) => HasClient (a :<|> b) where instance (HasClient a, HasClient b) => HasClient (a :<|> b) where
type Client (a :<|> b) = Client a :<|> Client b type Client (a :<|> b) = Client a :<|> Client b
clientWithRoute Proxy req = clientWithRoute Proxy req =
clientWithRoute (Proxy :: Proxy a) req :<|> clientWithRoute (Proxy :: Proxy a) req :<|>
clientWithRoute (Proxy :: Proxy b) req clientWithRoute (Proxy :: Proxy b) req

View file

@ -3,6 +3,7 @@
module Servant.Server where module Servant.Server where
import Data.Proxy import Data.Proxy
import Data.Text
import Network.HTTP.Types import Network.HTTP.Types
import Network.Wai import Network.Wai
@ -13,14 +14,19 @@ serve :: HasServer layout => Proxy layout -> Server layout -> Application
serve p server = toApplication (route p server) serve p server = toApplication (route p server)
toApplication :: RoutingApplication -> Application toApplication :: RoutingApplication -> Application
toApplication ra = \ request respond -> do toApplication ra request respond = do
m <- ra request ra (pathInfo request) request routingRespond
case m of where
Nothing -> respond $ responseLBS notFound404 [] "not found" routingRespond :: Maybe Response -> IO ResponseReceived
Just response -> respond response routingRespond Nothing =
respond $ responseLBS notFound404 [] "not found"
routingRespond (Just response) =
respond response
type RoutingApplication = type RoutingApplication =
Request -> IO (Maybe Response) [Text] -- ^ the unmodified 'pathInfo'
-> Request -- ^ the request, the field 'pathInfo' may be modified by url routing
-> (Maybe Response -> IO ResponseReceived) -> IO ResponseReceived
class HasServer layout where class HasServer layout where
type Server layout :: * type Server layout :: *

View file

@ -10,6 +10,7 @@ module Servant.ServerSpec where
import Data.Aeson import Data.Aeson
import Data.Proxy import Data.Proxy
import Data.String.Conversions
import GHC.Generics import GHC.Generics
import Network.HTTP.Types import Network.HTTP.Types
import Network.Wai import Network.Wai
@ -20,6 +21,7 @@ import Test.Hspec.Wai
import Servant.API.Get import Servant.API.Get
import Servant.API.GetParam import Servant.API.GetParam
import Servant.API.Post import Servant.API.Post
import Servant.API.Raw
import Servant.API.RQBody import Servant.API.RQBody
import Servant.API.Sub import Servant.API.Sub
import Servant.API.Union import Servant.API.Union
@ -60,6 +62,7 @@ spec = do
getSpec getSpec
getParamSpec getParamSpec
postSpec postSpec
rawSpec
unionSpec unionSpec
@ -67,7 +70,8 @@ type GetApi = Get Person
getApi :: Proxy GetApi getApi :: Proxy GetApi
getApi = Proxy getApi = Proxy
getSpec :: Spec = do getSpec :: Spec
getSpec = do
describe "Servant.API.Get" $ do describe "Servant.API.Get" $ do
with (return (serve getApi (return alice))) $ do with (return (serve getApi (return alice))) $ do
it "allows to GET a Person" $ do it "allows to GET a Person" $ do
@ -118,6 +122,32 @@ postSpec = do
} }
type RawApi = "foo" :> Raw
rawApi :: Proxy RawApi
rawApi = Proxy
rawApplication :: Show a => (Request -> a) -> Application
rawApplication f request respond = respond $ responseLBS ok200 [] (cs $ show $ f request)
rawSpec :: Spec
rawSpec = do
describe "Servant.API.Raw" $ do
it "runs applications" $ do
(flip runSession) (serve rawApi (rawApplication (const (42 :: Integer)))) $ do
response <- Network.Wai.Test.request defaultRequest{
pathInfo = ["foo"]
}
liftIO $ do
simpleBody response `shouldBe` "42"
it "gets the pathInfo unmodified" $ do
(flip runSession) (serve rawApi (rawApplication pathInfo)) $ do
response <- Network.Wai.Test.request defaultRequest{
pathInfo = ["foo"]
}
liftIO $ do
simpleBody response `shouldBe` cs (show ["foo" :: String])
type UnionApi = type UnionApi =
"foo" :> Get Person "foo" :> Get Person
:<|> "bar" :> Get Animal :<|> "bar" :> Get Animal