@@ -18,6 +18,7 @@ import Data.Aeson
18import Data.ByteString.Lazy (ByteString)
18import Data.ByteString.Lazy (ByteString)
19import Data.Monoid
19import Data.Monoid
20import Data.Proxy
20import Data.Proxy
21+import Data.String.Conversions
21import Data.Text
22import Data.Text
22import GHC.Generics
23import GHC.Generics
23import GHC.TypeLits
24import GHC.TypeLits
@@ -29,23 +30,32 @@ import Soenke
30import qualified Network.HTTP.Client as Http.Client
31import qualified Network.HTTP.Client as Http.Client
35class FromText a where
33class FromText a where
36− capture :: Text -> Maybe a
34+ fromText :: Text -> Maybe a
38class ToText a where
36class ToText a where
39 toText :: a -> Text
37 toText :: a -> Text
41instance FromText Text where
39instance FromText Text where
42− capture = Just
40+ fromText = Just
44instance ToText Text where
42instance ToText Text where
45 toText = id
43 toText = id
45+instance FromText Bool where
46+ fromText "true" = Just True
47+ fromText "false" = Just False
50+instance ToText Bool where
52+ toText False = "false"
47captured :: FromText a => proxy (Capture sym a) -> Text -> Maybe a
57captured :: FromText a => proxy (Capture sym a) -> Text -> Maybe a
48−captured _ = capture
58+captured _ = fromText
50instance (KnownSymbol capture, FromText a, HasServer sublayout)
60instance (KnownSymbol capture, FromText a, HasServer sublayout)
51 => HasServer (Capture capture a :> sublayout) where
61 => HasServer (Capture capture a :> sublayout) where
@@ -70,9 +80,11 @@ instance (KnownSymbol capture, ToText a, HasClient sublayout)
70 type Client (Capture capture a :> sublayout) =
80 type Client (Capture capture a :> sublayout) =
71 a -> Client sublayout
81 a -> Client sublayout
73− clientWithRoute Proxy path val =
83+ clientWithRoute Proxy req val =
74− clientWithRoute (Proxy :: Proxy sublayout)
84+ clientWithRoute (Proxy :: Proxy sublayout) $
75− (path ++ "/" ++ unpack (toText val))
87+ where p = unpack (toText val)
77-- * Request Body support
89-- * Request Body support
78data RQBody a
90data RQBody a
@@ -89,39 +101,54 @@ instance (FromJSON a, HasServer sublayout)
89 Nothing -> return Nothing
101 Nothing -> return Nothing
90 Just v -> route (Proxy :: Proxy sublayout) (subserver v) request
102 Just v -> route (Proxy :: Proxy sublayout) (subserver v) request
92−instance (ToJSON a, FromJSON b)
104+instance (ToJSON a, HasClient sublayout)
93− => HasClient (RQBody a :> Post b) where
105+ => HasClient (RQBody a :> sublayout) where
95− type Client (RQBody a :> Post b) =
107+ type Client (RQBody a :> sublayout) =
96− a -> URI -> EitherT String IO b
108+ a -> Client sublayout
98− clientWithRoute Proxy path body uri = do
110+ clientWithRoute Proxy req body =
99− partialRequest <- liftIO . Http.Client.parseUrl $
111+ clientWithRoute (Proxy :: Proxy sublayout) $
100− show ( nullURI { uriPath = path }
112+ setRQBody (encode body) req
104− let request = partialRequest
114+-- * GET params support (i.e query string arguments)
105− { Http.Client.method = methodPost
115+data GetParam sym a
106− , Http.Client.requestBody = Http.Client.RequestBodyLBS (encode body)
109− innerResponse <- liftIO . __withGlobalManager $ \ manager ->
117+instance (KnownSymbol sym, FromText a, HasServer sublayout)
110− Http.Client.httpLbs request manager
118+ => HasServer (GetParam sym a :> sublayout) where
112− when (Http.Client.responseStatus innerResponse /= status201) $
120+ type Server (GetParam sym a :> sublayout) =
113− left ("HTTP POST request failed with status: " ++ show (Http.Client.responseStatus innerResponse))
121+ Maybe a -> Server sublayout
115− maybe (left "HTTP POST request returned invalid json") return $
123+ route Proxy subserver request = do
116− decode' (Http.Client.responseBody innerResponse)
124+ let querytext = parseQueryText $ rawQueryString request
126+ case lookup paramName querytext of
127+ Nothing -> Nothing -- param absent from the query string
128+ Just Nothing -> Nothing -- param present with no value -> Nothing
129+ Just (Just v) -> fromText v -- if present, we try to convert to
118−-- * Example
132+ route (Proxy :: Proxy sublayout) (subserver param) request
120−type TestApi = "hello" :> Capture "name" Text :> Get Greet
134+ where paramName = cs $ symbolVal (Proxy :: Proxy sym)
121− :<|> "greet" :> RQBody Greet :> Post Greet
123−testApi :: Proxy TestApi
136+instance (KnownSymbol sym, ToText a, HasClient sublayout)
124−testApi = Proxy
137+ => HasClient (GetParam sym a :> sublayout) where
139+ type Client (GetParam sym a :> sublayout) =
140+ Maybe a -> Client sublayout
142+ -- if mparam = Nothing, we don't add it to the query string
143+ clientWithRoute Proxy req mparam =
144+ clientWithRoute (Proxy :: Proxy sublayout) $
145+ appendToQueryString pname mparamText req
147+ where pname = pack pname'
148+ pname' = symbolVal (Proxy :: Proxy sym)
149+ mparamText = fmap toText mparam
126data Greet = Greet { msg :: Text }
153data Greet = Greet { msg :: Text }
127 deriving (Generic, Show)
154 deriving (Generic, Show)
@@ -129,29 +156,47 @@ data Greet = Greet { msg :: Text }
129instance FromJSON Greet
156instance FromJSON Greet
130instance ToJSON Greet
157instance ToJSON Greet
161+ "hello" :> Capture "name" Text :> GetParam "capital" Bool :> Get Greet
162+ :<|> "greet" :> RQBody Greet :> Post Greet
164+testApi :: Proxy TestApi
167+-- Server-side handlers
132server :: Server TestApi
168server :: Server TestApi
133server = hello :<|> greet
169server = hello :<|> greet
171+ where hello name Nothing = hello name (Just False)
135− where hello = return . Greet . ("Hello, " <>)
172+ hello name (Just False) = return . Greet $ "Hello, " <> name
173+ hello name (Just True) = return . Greet . toUpper $ "Hello, " <> name
136 greet = return
175 greet = return
138−getGreet :: Text -> URI -> EitherT String IO Greet
177+-- Client-side query functions
139−postGreet :: Greet -> URI -> EitherT String IO Greet
178+clientApi :: Client TestApi
179+clientApi = client testApi
181+getGreet :: Text -> Maybe Bool -> URI -> EitherT String IO Greet
182+postGreet :: Greet -> URI -> EitherT String IO Greet
141−getGreet :<|> postGreet
183+getGreet :<|> postGreet = clientApi
185+-- Turn the server into a WAI app
144test :: Application
186test :: Application
145test = serve testApi server
187test = serve testApi server
147runTestServer :: Port -> IO ()
190runTestServer :: Port -> IO ()
148runTestServer port = run port test
191runTestServer port = run port test
193+-- Run some queries against the server
150runTest :: IO ()
194runTest :: IO ()
151runTest = do
195runTest = do
152 tid <- forkIO $ runTestServer 8001
196 tid <- forkIO $ runTestServer 8001
153− let Just uri = parseURI "http://localhost:8001/"
197+ let Just uri = parseURI "http://localhost:8001"
154− print =<< runEitherT (getGreet "alp" uri)
198+ print =<< runEitherT (getGreet "alp" (Just True) uri)
199+ print =<< runEitherT (getGreet "alp" (Just False) uri)
155 let g = Greet "yo"
200 let g = Greet "yo"
156 print =<< runEitherT (postGreet g uri)
201 print =<< runEitherT (postGreet g uri)
157 killThread tid
202 killThread tid