on master help sign in
Support QueryParams and QueryFlag, add examples
commit364ab94d2a873bca5428f7585c9c468fdff30c24 authorGreg Hale <imalsogreg@gmail.com> dateSun 6 Mar 2016, 23:28 (11 years ago) parent43ad24b
5 files changed+99−55
exec/API.hs+4−1 exec/Example.hs+30−16 src/Servant/Common/Req.hs+39−19 src/Servant/Reflex.hs+12−15 testserver/Main.hs+14−4
exec/API.hs+4−1
@@ -8,5 +8,8 @@ import Servant.API
88-- | API spec for server, client, and docs
99type API = "getunit" :> Get '[JSON] ()
1010 :<|> "getint" :> Get '[JSON] Int
11− :<|> "sayhi" :> QueryParam "username" String :> Get '[JSON] String
11+ :<|> "sayhi" :> QueryParam "username" String
12+ :> QueryParams "greetings" String
13+ :> QueryFlag "gusto"
14+ :> Get '[JSON] String
1215 :<|> Raw
exec/Example.hs+30−16
@@ -28,28 +28,42 @@ run = do
2828 let (getUnit :<|> getInt :<|> sayhi :<|> doRaw) =
2929 client api (Proxy :: Proxy m) (constDyn url)
3030
31− unitBtn <- button "Get unit"
32− intBtn <- button "Get int"
31+ elClass "div" "demo-group" $ do
32+ unitBtn <- button "Get unit"
33+ intBtn <- button "Get int"
3334
34− unitResponse <- getUnit unitBtn
35− intResponse :: Event t (Maybe Int, XhrResponse) <- getInt intBtn
35+ unitResponse <- getUnit unitBtn
36+ intResponse :: Event t (Maybe Int, XhrResponse) <- getInt intBtn
3637
37− score <- foldDyn (+) 0 (fmapMaybe fst intResponse)
38+ score <- foldDyn (+) 0 (fmapMaybe fst intResponse)
3839
39− r <- holdDyn "Waiting" $
40+ r <- holdDyn "Waiting" $
4041 leftmost [fmap (showXhrResponse . snd) unitResponse
4142 ,fmap (showXhrResponse . snd) intResponse
4243 ]
43− dynText r >> el "br" (return ()) >> text "Total: " >> display score
44−
45− el "br" $ return ()
46− text "Name"
47− inp :: Dynamic t String <- fmap value (textInput def)
48− let checkedinp = fmap (\i -> bool (Just i) Nothing (null i)) (current inp)
49− sayhiClicks :: Event t () <- button "Say hi"
50− resp <- fmap fst <$> sayhi checkedinp sayhiClicks
51− el "br" $ return ()
52− dynText =<< holdDyn "No hi yet" (fmapMaybe id resp)
44+ dynText r >> el "br" (return ()) >> text "Total: " >> display score
45+
46+ elClass "div" "demo-group" $ do
47+
48+ text "Name"
49+ el "br" $ return ()
50+ inp :: Dynamic t String <- fmap value (textInput def)
51+ let checkedName = fmap (\i -> bool (Just i) Nothing (null i)) (current inp)
52+ el "br" $ return ()
53+
54+ text "Greetings (space-separated)"
55+ el "br" $ return ()
56+ greetings <- fmap (fmap words . current . value) (textInput def)
57+
58+ el "br" $ return ()
59+
60+ gusto <- value <$> checkbox False def
61+
62+ el "br" $ return ()
63+ sayhiClicks :: Event t () <- button "Say hi"
64+
65+ resp <- fmap fst <$> sayhi checkedName greetings (current gusto) sayhiClicks
66+ dynText =<< holdDyn "No hi yet" (fmapMaybe id resp)
5367
5468showXhrResponse :: XhrResponse -> String
5569showXhrResponse (XhrResponse stat stattxt rbmay rtmay) =
src/Servant/Common/Req.hs+39−19
@@ -1,17 +1,14 @@
11{-# LANGUAGE DeriveDataTypeable #-}
22{-# LANGUAGE CPP #-}
33{-# LANGUAGE OverloadedStrings #-}
4+{-# LANGUAGE LambdaCase #-}
45{-# LANGUAGE ScopedTypeVariables #-}
56module Servant.Common.Req where
67
7−-- import Control.Exception
8−-- import Control.Monad
9−-- import Control.Monad.Catch (MonadThrow)
10−-- import Control.Monad.IO.Class
11−-- import Control.Monad.Trans.Except
128import Control.Applicative (liftA2)
139import Data.ByteString.Char8 hiding (pack, filter, map, null, elem)
1410import qualified Data.ByteString.Lazy.Char8 as BL
11+import Data.Maybe
1512import qualified Data.Text.Encoding as TE
1613-- import qualified Data.Foldable as F
1714import qualified Data.List as L
@@ -64,9 +61,13 @@ import Web.HttpApiData
6461
6562-- instance Exception ServantError
6663
64+data QueryPart t = QueryPartParam (Behavior t [String])
65+ | QueryPartFlag (Behavior t Bool)
66+
6767data Req t = Req
6868 { reqPathParts :: [Behavior t (Maybe String)]
69− , qParams :: [(String, Behavior t [String])]
69+ -- , qParams :: [(String, Behavior t [QueryPart])]
70+ , qParams :: [(String, QueryPart t)]
7071 , reqBody :: Maybe (ByteString, String)
7172 -- , reqAccept :: [MediaType]
7273 , headers :: [(String, Behavior t String)]
@@ -103,21 +104,40 @@ performRequest reqMethod req _ trigger = do
103104 let urlParts :: Behavior t (Maybe [String]) = fmap sequence t
104105 let urlPath :: Behavior t (Maybe String) = (fmap.fmap) (L.intercalate "/") urlParts
105106
106− let oneNamedPair :: String -> [String] -> String
107− oneNamedPair pName ps =
108− L.intercalate "&" $ map (\p -> pName ++ "=" ++ p) ps
107+ queryPartString :: (String, QueryPart t) -> Behavior t (Maybe String)
108+ queryPartString (pName, qp) = case qp of
109+ QueryPartParam ps -> ffor ps $ \pStrings -> -- case null pStrings of
110+ if null pStrings
111+ then Nothing
112+ else Just (L.intercalate "&" (fmap (\p -> pName ++ '=' : p) pStrings))
113+ QueryPartFlag fl -> ffor fl $ \case
114+ True -> Just pName
115+ False -> Nothing
116+
117+ queryPartStrings = map queryPartString (qParams req)
118+ queryPartStrings' = sequence queryPartStrings :: Behavior t [Maybe String]
119+ queryString :: Behavior t (Maybe String ) =
120+ ffor queryPartStrings' $ \qs -> Just (L.intercalate "&" (catMaybes qs))
121+ xhrUrl = (liftA2 . liftA2) (\p q -> p ++ '?' : q) urlPath queryString
122+ xhrReq = (fmap . fmap) (\p -> XhrRequest reqMethod p def) xhrUrl
123+
124+ performRequestAsync (fmapMaybe id $ tag xhrReq trigger)
109125
110− t' :: [Behavior t String]
111− t' = map (\(pName, pVals) -> fmap (oneNamedPair pName) pVals)
112− (qParams req)
126+ -- let oneNamedPair :: String -> [QueryPart] -> String
127+ -- oneNamedPair pName ps =
128+ -- L.intercalate "&" $ ffor ps $ \case
129+ -- QueryPartParam pval -> pName ++ "=" ++ pval
130+ -- QueryPartFlag True -> pName
131+ -- QueryPartFlag False -> error "Impossible case"
113132
114− queryString :: Behavior t String
115− queryString = fmap (L.intercalate "&") (sequence t')
133+ -- t' :: [Behavior t String]
134+ -- t' = map (\(pName, pVals) -> fmap (oneNamedPair pName) pVals)
135+ -- (qParams req)
116136
117− xhrUrl = (liftA2 . liftA2) (\u q -> u ++ '?' : q) urlPath (fmap Just queryString)
118− xhrReq = (fmap . fmap) (\p -> XhrRequest reqMethod p def) xhrUrl
137+ -- queryString :: Behavior t String
138+ -- queryString = fmap (L.intercalate "&") (sequence t')
119139
120− performRequestAsync (fmapMaybe id $ tag xhrReq trigger)
140+ -- xhrUrl = (liftA2 . liftA2) (\u q -> u ++ '?' : q) urlPath (fmap Just queryString)
121141
122142-- TODO implement
123143-- => String -> Req -> BaseUrl -> ExceptT ServantError IO [HTTP.Header]
@@ -137,9 +157,9 @@ performRequestCT ct reqMethod req reqHost trigger = do
137157 return $ ffor resp $ \xhr ->
138158 (hushed (mimeUnrender ct . BL.fromStrict . TE.encodeUtf8)
139159 =<< _xhrResponse_responseText xhr, xhr)
140− where hushed :: (x -> Either e y) -> (x -> Maybe y)
160+ where hushed :: (x -> Either e y) -> x -> Maybe y
141161 hushed f ea = case f ea of
142− Left e -> Nothing
162+ Left _ -> Nothing
143163 Right a -> Just a
144164
145165
src/Servant/Reflex.hs+12−15
@@ -245,7 +245,7 @@ instance (KnownSymbol sym, ToHttpApiData a, HasClient t m sublayout, Reflex t)
245245 (req {qParams = paramPair : qParams req}) baseurl
246246
247247 where pname = symbolVal (Proxy :: Proxy sym)
248− p prm = fmap maybeToList $ (fmap . fmap) (unpack . toQueryParam) prm
248+ p prm = QueryPartParam $ fmap maybeToList $ (fmap . fmap) (unpack . toQueryParam) prm
249249 paramPair = (pname, p mparam)
250250
251251-- | If you use a 'QueryParams' in one of your endpoints in your API,
@@ -287,8 +287,8 @@ instance (KnownSymbol sym, ToHttpApiData a, HasClient t m sublayout, Reflex t)
287287
288288 where req' = req { qParams = (pname, params') : qParams req }
289289 pname = symbolVal (Proxy :: Proxy sym)
290− params' = (fmap . fmap) (unpack . toQueryParam)
291− paramlist :: Behavior t [String]
290+ params' = QueryPartParam $ (fmap . fmap) (unpack . toQueryParam)
291+ paramlist
292292
293293
294294-- | If you use a 'QueryFlag' in one of your endpoints in your API,
@@ -315,21 +315,18 @@ instance (KnownSymbol sym, ToHttpApiData a, HasClient t m sublayout, Reflex t)
315315-- > -- 'getBooksBy True' to only get _already published_ books
316316
317317-- TODO Bring back
318−-- instance (KnownSymbol sym, HasClient t m sublayout)
319−-- => HasClient t m (QueryFlag sym :> sublayout) where
318+instance (KnownSymbol sym, HasClient t m sublayout, Reflex t)
319+ => HasClient t m (QueryFlag sym :> sublayout) where
320320
321−-- type Client t m (QueryFlag sym :> sublayout) =
322−-- Bool -> Client t m sublayout
321+ type Client t m (QueryFlag sym :> sublayout) =
322+ Behavior t Bool -> Client t m sublayout
323323
324−-- clientWithRoute Proxy req baseurl flag =
325−-- clientWithRoute (Proxy :: Proxy sublayout)
326−-- (if flag
327−-- then appendToQueryString paramname Nothing req
328−-- else req
329−-- )
330−-- baseurl
324+ clientWithRoute Proxy q req baseurl flag =
325+ clientWithRoute (Proxy :: Proxy sublayout) q req' baseurl
331326
332−-- where paramname = cs $ symbolVal (Proxy :: Proxy sym)
327+ where req' = req { qParams = thisPair : qParams req }
328+ thisPair = (pName, QueryPartFlag flag) :: (String, QueryPart t)
329+ pName = symbolVal (Proxy :: Proxy sym)
333330
334331
335332-- | Pick a 'Method' and specify where the server you want to query is. You get
testserver/Main.hs+14−4
@@ -6,9 +6,12 @@
66{-# LANGUAGE TypeOperators #-}
77
88import Data.Aeson
9+import Data.Bool
10+import Data.Char (toUpper)
11+import qualified Data.List as L
912import Data.Monoid
1013import Data.Proxy
11−import Data.Text
14+import Data.Text hiding (length, null, map, head, toUpper)
1215import GHC.Generics
1316import Snap.Http.Server
1417import Snap.Core
@@ -42,9 +45,16 @@ data App = App
4245-- Each handler runs in the 'ExceptT ServantErr IO' monad.
4346server :: Server API (Handler App App)
4447server = return () :<|> return 100 :<|> sayhi :<|> serveDirectory "static"
45− where sayhi nm = case nm of
46− Nothing -> return "Sorry, who are you?"
47− Just n -> return $ "Hi, " <> n <> "!"
48+ where sayhi nm greetings withGusto = case nm of
49+ Nothing -> return ("Sorry, who are you?" :: String)
50+ Just n -> do
51+ let modifier = bool id (map toUpper) withGusto
52+ greetPart
53+ | null greetings = "Hi, "
54+ | length greetings == 1 = L.head greetings ++ ", "
55+ | otherwise = L.intercalate ", " (L.init greetings)
56+ ++ ", and " ++ L.last greetings ++ ", "
57+ return . modifier $ greetPart ++ n
4858
4959-- Turn the server into a WAI app. 'serve' is provided by servant,
5060-- more precisely by the Servant.Server module.