@@ -3,95 +3,155 @@
3{-# LANGUAGE TypeFamilies #-}
3{-# LANGUAGE TypeFamilies #-}
4{-# LANGUAGE DeriveGeneric #-}
4{-# LANGUAGE DeriveGeneric #-}
5{-# LANGUAGE TypeOperators #-}
5{-# LANGUAGE TypeOperators #-}
6+{-# LANGUAGE FlexibleContexts #-}
6{-# LANGUAGE FlexibleInstances #-}
7{-# LANGUAGE FlexibleInstances #-}
7{-# LANGUAGE OverloadedStrings #-}
8{-# LANGUAGE OverloadedStrings #-}
8{-# LANGUAGE ScopedTypeVariables #-}
9{-# LANGUAGE ScopedTypeVariables #-}
9module Servant where
10module Servant where
11import Control.Applicative
12import Control.Applicative
13+import Control.Concurrent (forkIO, killThread)
15+import Control.Monad.IO.Class
16+import Control.Monad.Trans.Either
12import Data.Aeson
17import Data.Aeson
18+import Data.ByteString.Lazy (ByteString)
13import Data.Monoid
19import Data.Monoid
14import Data.Proxy
20import Data.Proxy
15import Data.Text
21import Data.Text
16import GHC.Generics
22import GHC.Generics
17import GHC.TypeLits
23import GHC.TypeLits
24+import Network.HTTP.Types
18import Network.Wai
26import Network.Wai
19import Network.Wai.Handler.Warp
27import Network.Wai.Handler.Warp
20import Soenke
28import Soenke
30+import qualified Network.HTTP.Client as Http.Client
22-- * Captures
32-- * Captures
23data Capture sym a
33data Capture sym a
25class FromText a where
35class FromText a where
26 capture :: Text -> Maybe a
36 capture :: Text -> Maybe a
28instance FromText Text where
41instance FromText Text where
29 capture = Just
42 capture = Just
44+instance ToText Text where
31captured :: FromText a => proxy (Capture sym a) -> Text -> Maybe a
47captured :: FromText a => proxy (Capture sym a) -> Text -> Maybe a
32captured _ = capture
48captured _ = capture
34instance (KnownSymbol capture, FromText a, HasServer sublayout)
50instance (KnownSymbol capture, FromText a, HasServer sublayout)
35 => HasServer (Capture capture a :> sublayout) where
51 => HasServer (Capture capture a :> sublayout) where
37− -- this means that what follows the capture
38− -- must necessarily be a function that expects
39− -- the captured value. :-(
40 type Server (Capture capture a :> sublayout) =
53 type Server (Capture capture a :> sublayout) =
41− Capture capture a :> (a -> Server sublayout)
54+ a -> Server sublayout
43− route Proxy (capture :> subserver) request = case pathInfo request of
56+ route Proxy subserver request = case pathInfo request of
44 (first : rest)
57 (first : rest)
45− -> case captured capture first of
58+ -> case captured captureProxy first of
46 Nothing -> return Nothing
59 Nothing -> return Nothing
47 Just v -> route (Proxy :: Proxy sublayout) (subserver v) request{
60 Just v -> route (Proxy :: Proxy sublayout) (subserver v) request{
48 pathInfo = rest
61 pathInfo = rest
50 _ -> return Nothing
63 _ -> return Nothing
65+ where captureProxy = Proxy :: Proxy (Capture capture a)
67+instance (KnownSymbol capture, ToText a, HasClient sublayout)
68+ => HasClient (Capture capture a :> sublayout) where
70+ type Client (Capture capture a :> sublayout) =
71+ a -> Client sublayout
73+ clientWithRoute Proxy path val =
74+ clientWithRoute (Proxy :: Proxy sublayout)
75+ (path ++ "/" ++ unpack (toText val))
52-- * Request Body support
77-- * Request Body support
53data RQBody a
78data RQBody a
55instance (FromJSON a, HasServer sublayout)
80instance (FromJSON a, HasServer sublayout)
56 => HasServer (RQBody a :> sublayout) where
81 => HasServer (RQBody a :> sublayout) where
58− -- same caveat as for the Captures...
59 type Server (RQBody a :> sublayout) =
83 type Server (RQBody a :> sublayout) =
60− RQBody a :> (a -> Server sublayout)
84+ a -> Server sublayout
62− route Proxy (rqbody :> subserver) request = do
86+ route Proxy subserver request = do
63 mrqbody <- decode' <$> lazyRequestBody request
87 mrqbody <- decode' <$> lazyRequestBody request
64 case mrqbody of
88 case mrqbody of
65 Nothing -> return Nothing
89 Nothing -> return Nothing
66 Just v -> route (Proxy :: Proxy sublayout) (subserver v) request
90 Just v -> route (Proxy :: Proxy sublayout) (subserver v) request
92+instance (ToJSON a, FromJSON b)
93+ => HasClient (RQBody a :> Post b) where
95+ type Client (RQBody a :> Post b) =
96+ a -> URI -> EitherT String IO b
98+ clientWithRoute Proxy path body uri = do
99+ partialRequest <- liftIO . Http.Client.parseUrl $
100+ show ( nullURI { uriPath = path }
104+ let request = partialRequest
105+ { Http.Client.method = methodPost
106+ , Http.Client.requestBody = Http.Client.RequestBodyLBS (encode body)
109+ innerResponse <- liftIO . __withGlobalManager $ \ manager ->
110+ Http.Client.httpLbs request manager
112+ when (Http.Client.responseStatus innerResponse /= status201) $
113+ left ("HTTP POST request failed with status: " ++ show (Http.Client.responseStatus innerResponse))
115+ maybe (left "HTTP POST request returned invalid json") return $
116+ decode' (Http.Client.responseBody innerResponse)
68-- * Example
118-- * Example
70type TestApi = "hello" :> Capture "name" Text :> Get Greet
120type TestApi = "hello" :> Capture "name" Text :> Get Greet
121+ :<|> "greet" :> RQBody Greet :> Post Greet
72testApi :: Proxy TestApi
123testApi :: Proxy TestApi
73testApi = Proxy
124testApi = Proxy
75data Greet = Greet { msg :: Text }
126data Greet = Greet { msg :: Text }
76− deriving Generic
127+ deriving (Generic, Show)
129+instance FromJSON Greet
78instance ToJSON Greet
130instance ToJSON Greet
80server :: Server TestApi
132server :: Server TestApi
81−server =
133+server = hello :<|> greet
82− Proxy -- :: Proxy "hello"
134+
83− :> Proxy -- :: Proxy (Capture "name" Text)
135+ where hello = return . Greet . ("Hello, " <>)
84− :> (return . func)
136+ greet = return
86− where func name = Greet ("Hello, " <> name)
138+getGreet :: Text -> URI -> EitherT String IO Greet
139+postGreet :: Greet -> URI -> EitherT String IO Greet
141+getGreet :<|> postGreet
88test :: Application
144test :: Application
89test = serve testApi server
145test = serve testApi server
91−runTest :: Port -> IO ()
147+runTestServer :: Port -> IO ()
92−runTest port = run port test
148+runTestServer port = run port test
94−-- load in ghci, call 'runTest 8000'
150+runTest :: IO ()
95−-- visit http://localhost:8000/hello/world
151+runTest = do
96−-- visit http://localhost:8000/hello/soenke
152+ tid <- forkIO $ runTestServer 8001
97−-- visit http://localhost:8000/hello/alp
153+ let Just uri = parseURI "http://localhost:8001/"
154+ print =<< runEitherT (getGreet "alp" uri)
156+ print =<< runEitherT (postGreet g uri)