@@ -7,6 +7,7 @@
7{-# LANGUAGE LambdaCase #-}
7{-# LANGUAGE LambdaCase #-}
8{-# LANGUAGE MultiParamTypeClasses #-}
8{-# LANGUAGE MultiParamTypeClasses #-}
9{-# LANGUAGE OverloadedStrings #-}
9{-# LANGUAGE OverloadedStrings #-}
10+{-# LANGUAGE RankNTypes #-}
10{-# LANGUAGE RecordWildCards #-}
11{-# LANGUAGE RecordWildCards #-}
11{-# LANGUAGE ScopedTypeVariables #-}
12{-# LANGUAGE ScopedTypeVariables #-}
12{-# LANGUAGE TypeFamilies #-}
13{-# LANGUAGE TypeFamilies #-}
@@ -23,7 +24,8 @@
24module Network.Google.Client where
25module Network.Google.Client where
26−import Control.Exception
27+import Control.Monad.Catch
28+-- import Control.Exception
27import Control.Lens
29import Control.Lens
28import Control.Monad.Trans.Class
30import Control.Monad.Trans.Class
29import Control.Monad.Trans.Reader
31import Control.Monad.Trans.Reader
@@ -50,9 +52,8 @@ import GHC.Generics
50import GHC.TypeLits
52import GHC.TypeLits
51import Network.Google.Types
53import Network.Google.Types
52import Network.HTTP.Client (HttpException)
54import Network.HTTP.Client (HttpException)
53−import Network.HTTP.Conduit (Manager)
54−import qualified Network.HTTP.Conduit as Client
55+import qualified Network.HTTP.Client as Client
55−import Network.HTTP.Media
56+import Network.HTTP.Media hiding (Accept)
56import Network.HTTP.Types
57import Network.HTTP.Types
57import Servant.API hiding (Header)
58import Servant.API hiding (Header)
@@ -71,45 +72,6 @@ type ResponseBody = ResumableSource (ResourceT IO) ByteString
71data Body = Body -- MediaType
72data Body = Body -- MediaType
72 deriving (Eq, Show, Generic, Data, Typeable)
73 deriving (Eq, Show, Generic, Data, Typeable)
74−data Request = Request
75− { _rqHost :: ByteString
77− , _rqBasePath :: ByteString
79− , _rqQuery :: DList (Text, Maybe Text)
80− , _rqHeaders :: [Header]
81− , _rqBody :: Maybe (MediaType, LBS.ByteString)
84−initialRequest :: ByteString -- ^ Host.
86− -> ByteString -- ^ Base Path.
88−initialRequest h n p = Request
94− , _rqHeaders = mempty
98−appendPath :: Request -> Builder -> Request
99−appendPath rq x = rq { _rqPath = _rqPath rq <> "/" <> x }
101−appendPaths :: ToText a => Request -> [a] -> Request
102−appendPaths rq = appendPath rq . foldMap (mappend "/" . buildText)
104−appendQuery :: Request -> Text -> Maybe Text -> Request
105−appendQuery rq k v = rq { _rqQuery = DList.snoc (_rqQuery rq) (k, v) }
107−setBody :: Request -> MediaType -> LBS.ByteString -> Request
108−setBody rq c x = rq { _rqBody = Just (c, x) }
110−-- setAccept :: Reuqest -> [MediaType] -> Request
111−-- setAccept rq xs = rq { _rqAccept = xs }
113data Error
75data Error
114 = TransportError HttpException
76 = TransportError HttpException
115 | SerializeError SerializeError
77 | SerializeError SerializeError
@@ -167,50 +129,142 @@ instance AsError Error where
167 ServiceError e -> Right e
129 ServiceError e -> Right e
168 x -> Left x
130 x -> Left x
132+-- | Multiple path captures, with @[xs]@ forming @/<x1>/<x2>/<x2>/...@.
170data Captures (s :: Symbol) a
133data Captures (s :: Symbol) a
171 deriving (Typeable)
134 deriving (Typeable)
136+-- | Form a Google style sub-resource, such as @/<capture>:<mode>@.
173data CaptureMode (s :: Symbol) (m :: Symbol) a
137data CaptureMode (s :: Symbol) (m :: Symbol) a
174 deriving (Typeable)
138 deriving (Typeable)
176data MultipartRelated (cs :: [*]) m b
140data MultipartRelated (cs :: [*]) m b
177 deriving (Typeable)
141 deriving (Typeable)
179−instance ( MimeRender c m
143+-- | An intermediary request builder.
180− , GoogleClient fn
144+data RequestBuilder = RequestBuilder
181− ) => GoogleClient (MultipartRelated (c ': cs) m b :> fn) where
145+ { _rqHost :: ByteString
182− type Fn (MultipartRelated (c ': cs) m b :> fn) = m -> b -> Fn fn
146+ , _rqBasePath :: ByteString
147+ , _rqPath :: Builder
148+ , _rqQuery :: DList (Text, Maybe Text)
149+ , _rqBody :: Maybe (MediaType, LBS.ByteString)
184− -- requestBuild Proxy rq m b = requestBuild (Proxy :: Proxy fn) $
152+-- | Construct a default request builder. Since the Google service descriptions
185− -- mimeRender p m
153+-- have http/https in the host (rootUrl), the scheme is assumed.
186− -- mimeRender (Proxy :: Proxy OctetStream) b
154+defaultRequest :: ByteString -- ^ Host. (rootUrl)
187− -- where
155+ -> ByteString -- ^ Path. (servicePath)
188− -- p = Proxy :: Proxy c
156+ -> RequestBuilder
157+defaultRequest h p = RequestBuilder
165+appendPath :: RequestBuilder -> Builder -> RequestBuilder
166+appendPath rq x = rq { _rqPath = _rqPath rq <> "/" <> x }
168+appendPaths :: ToText a => RequestBuilder -> [a] -> RequestBuilder
169+appendPaths rq = appendPath rq . foldMap (mappend "/" . buildText)
171+appendQuery :: RequestBuilder -> Text -> Maybe Text -> RequestBuilder
172+appendQuery rq k v = rq { _rqQuery = DList.snoc (_rqQuery rq) (k, v) }
174+setBody :: RequestBuilder -> MediaType -> LBS.ByteString -> RequestBuilder
175+setBody rq c x = rq { _rqBody = Just (c, x) }
177+-- | A materialised 'http-client' request and associated response parser.
178+data Client a = Client
179+ { _clientAccept :: Maybe MediaType
180+ , _clientCheck :: Int -> Bool
181+ , _clientResponse :: ResponseBody -> ResourceT IO (Either SerializeError a)
182+ , _clientRequest :: ClientRequest
185+client :: (ResponseBody -> ResourceT IO (Either SerializeError a))
191+client f cs m ns rq = Client
192+ { _clientAccept = cs
193+ , _clientCheck = (`elem` ns)
194+ , _clientResponse = f
195+ , _clientRequest = clientRequest cs m rq
198+clientRequest :: Maybe MediaType -> Method -> RequestBuilder -> ClientRequest
199+clientRequest cs m RequestBuilder {..} = def
201+ , Client.checkStatus = \_ _ _ -> Nothing
204+clientMime :: MimeUnrender c a
210+clientMime p = client go (Just (contentType p))
214+ case mimeUnrender p lbs of
216+ Right x -> pure (Right x)
218+clientStream :: Accept c
223+ -> Client ResponseBody
224+clientStream p = client (pure . Right) (Just (contentType p))
226+clientEmpty :: Method -> [Int] -> RequestBuilder -> Client ()
227+clientEmpty = client go Nothing
229+ go b = closeResumableSource b >> pure (Right ())
231+-- sinkLBS :: ResponseBody -> Client LBS.ByteString
232+sinkLBS = fmap LBS.fromChunks . ($$+- CL.consume)
191class GoogleRequest a where
234class GoogleRequest a where
192 type Rs a :: *
235 type Rs a :: *
194− request :: a -> IO (Either Error (Rs a))
237+ request :: a -> Client (Rs a)
195− requestWith :: Request -> a -> IO (Either Error (Rs a))
238+ requestWith :: RequestBuilder -> a -> Client (Rs a)
197class GoogleClient fn where
240class GoogleClient fn where
198 type Fn fn :: *
241 type Fn fn :: *
200− requestBuild :: Proxy fn -> Request -> Fn fn
243+ clientBuild :: Proxy fn -> RequestBuilder -> Fn fn
245+instance ( MimeRender c m
247+ ) => GoogleClient (MultipartRelated (c ': cs) m b :> fn) where
248+ type Fn (MultipartRelated (c ': cs) m b :> fn) = m -> b -> Fn fn
250+ -- requestBuild Proxy rq m b = requestBuild (Proxy :: Proxy fn) $
252+ -- mimeRender (Proxy :: Proxy OctetStream) b
254+ -- p = Proxy :: Proxy c
202instance (KnownSymbol s, GoogleClient fn) => GoogleClient (s :> fn) where
256instance (KnownSymbol s, GoogleClient fn) => GoogleClient (s :> fn) where
203 type Fn (s :> fn) = Fn fn
257 type Fn (s :> fn) = Fn fn
205− requestBuild Proxy rq = requestBuild (Proxy :: Proxy fn) $
259+ clientBuild Proxy rq = clientBuild (Proxy :: Proxy fn) $
206 appendPath rq (buildSymbol (Proxy :: Proxy s))
260 appendPath rq (buildSymbol (Proxy :: Proxy s))
208instance (GoogleClient a, GoogleClient b) => GoogleClient (a :<|> b) where
262instance (GoogleClient a, GoogleClient b) => GoogleClient (a :<|> b) where
209 type Fn (a :<|> b) = Fn a :<|> Fn b
263 type Fn (a :<|> b) = Fn a :<|> Fn b
211− requestBuild Proxy rq =
265+ clientBuild Proxy rq =
212− requestBuild (Proxy :: Proxy a) rq
266+ clientBuild (Proxy :: Proxy a) rq
213− :<|> requestBuild (Proxy :: Proxy b) rq
267+ :<|> clientBuild (Proxy :: Proxy b) rq
215instance ( KnownSymbol s
269instance ( KnownSymbol s
216 , ToText a
270 , ToText a
@@ -218,7 +272,7 @@ instance ( KnownSymbol s
218 ) => GoogleClient (Capture s a :> fn) where
272 ) => GoogleClient (Capture s a :> fn) where
219 type Fn (Capture s a :> fn) = a -> Fn fn
273 type Fn (Capture s a :> fn) = a -> Fn fn
221− requestBuild Proxy rq = requestBuild (Proxy :: Proxy fn)
275+ clientBuild Proxy rq = clientBuild (Proxy :: Proxy fn)
222 . appendPath rq
276 . appendPath rq
223 . buildText
277 . buildText
@@ -228,7 +282,7 @@ instance ( KnownSymbol s
228 ) => GoogleClient (Captures s a :> fn) where
282 ) => GoogleClient (Captures s a :> fn) where
229 type Fn (Captures s a :> fn) = [a] -> Fn fn
283 type Fn (Captures s a :> fn) = [a] -> Fn fn
231− requestBuild Proxy rq = requestBuild (Proxy :: Proxy fn)
285+ clientBuild Proxy rq = clientBuild (Proxy :: Proxy fn)
232 . appendPaths rq
286 . appendPaths rq
234instance ( KnownSymbol s
288instance ( KnownSymbol s
@@ -238,7 +292,7 @@ instance ( KnownSymbol s
238 ) => GoogleClient (CaptureMode s m a :> fn) where
292 ) => GoogleClient (CaptureMode s m a :> fn) where
239 type Fn (CaptureMode s m a :> fn) = a -> Fn fn
293 type Fn (CaptureMode s m a :> fn) = a -> Fn fn
241− requestBuild Proxy rq x = requestBuild (Proxy :: Proxy fn)
295+ clientBuild Proxy rq x = clientBuild (Proxy :: Proxy fn)
242 . appendPath rq
296 . appendPath rq
243 $ buildText x <> buildSymbol (Proxy :: Proxy m)
297 $ buildText x <> buildSymbol (Proxy :: Proxy m)
@@ -248,7 +302,7 @@ instance ( KnownSymbol s
248 ) => GoogleClient (QueryParam s a :> fn) where
302 ) => GoogleClient (QueryParam s a :> fn) where
249 type Fn (QueryParam s a :> fn) = Maybe a -> Fn fn
303 type Fn (QueryParam s a :> fn) = Maybe a -> Fn fn
251− requestBuild Proxy rq mx = requestBuild (Proxy :: Proxy fn) $
305+ clientBuild Proxy rq mx = clientBuild (Proxy :: Proxy fn) $
252 case mx of
306 case mx of
253 Nothing -> rq
307 Nothing -> rq
254 Just x -> appendQuery rq k v
308 Just x -> appendQuery rq k v
@@ -262,7 +316,7 @@ instance ( KnownSymbol s
262 ) => GoogleClient (QueryParams s a :> fn) where
316 ) => GoogleClient (QueryParams s a :> fn) where
263 type Fn (QueryParams s a :> fn) = [a] -> Fn fn
317 type Fn (QueryParams s a :> fn) = [a] -> Fn fn
265− requestBuild Proxy rq = requestBuild (Proxy :: Proxy fn) . foldl' go rq
319+ clientBuild Proxy rq = clientBuild (Proxy :: Proxy fn) . foldl' go rq
267 go r = appendQuery r k . Just . toText
321 go r = appendQuery r k . Just . toText
@@ -273,7 +327,7 @@ instance ( MimeRender c a
273 ) => GoogleClient (ReqBody (c ': cs) a :> fn) where
327 ) => GoogleClient (ReqBody (c ': cs) a :> fn) where
274 type Fn (ReqBody (c ': cs) a :> fn) = a -> Fn fn
328 type Fn (ReqBody (c ': cs) a :> fn) = a -> Fn fn
276− requestBuild Proxy rq = requestBuild (Proxy :: Proxy fn)
330+ clientBuild Proxy rq = clientBuild (Proxy :: Proxy fn)
277 . setBody rq (contentType p)
331 . setBody rq (contentType p)
278 . mimeRender p
332 . mimeRender p
@@ -284,128 +338,89 @@ instance ( MimeRender c a
284instance MimeUnrender c a => GoogleClient (Get (c ': cs) a) where
338instance MimeUnrender c a => GoogleClient (Get (c ': cs) a) where
285 type Fn (Get (c ': cs) a) = Client a
339 type Fn (Get (c ': cs) a) = Client a
287− requestBuild Proxy = requestMime (Proxy :: Proxy c) methodGet [200, 203]
341+ clientBuild Proxy = clientMime (Proxy :: Proxy c) methodGet [200, 203]
289−instance GoogleClient (Get (c ': cs) ResponseBody) where
343+instance Accept c => GoogleClient (Get (c ': cs) ResponseBody) where
290 type Fn (Get (c ': cs) ResponseBody) = Client ResponseBody
344 type Fn (Get (c ': cs) ResponseBody) = Client ResponseBody
292− requestBuild Proxy = requestBody (Proxy :: Proxy c) methodGet [200, 203]
346+ clientBuild Proxy = clientStream (Proxy :: Proxy c) methodGet [200, 203]
294instance GoogleClient (Get (c ': cs) ()) where
348instance GoogleClient (Get (c ': cs) ()) where
295 type Fn (Get (c ': cs) ()) = Client ()
349 type Fn (Get (c ': cs) ()) = Client ()
297− requestBuild Proxy = requestMime (Proxy :: Proxy c) methodGet [204]
351+ clientBuild Proxy = clientEmpty methodGet [204]
299instance MimeUnrender c a => GoogleClient (Post (c ': cs) a) where
353instance MimeUnrender c a => GoogleClient (Post (c ': cs) a) where
300 type Fn (Post (c ': cs) a) = Client a
354 type Fn (Post (c ': cs) a) = Client a
302− requestBuild Proxy = requestMime (Proxy :: Proxy c) methodPost [200, 201]
356+ clientBuild Proxy = clientMime (Proxy :: Proxy c) methodPost [200, 201]
304instance GoogleClient (Post (c ': cs) ()) where
358instance GoogleClient (Post (c ': cs) ()) where
305 type Fn (Post (c ': cs) ()) = Client ()
359 type Fn (Post (c ': cs) ()) = Client ()
307− requestBuild Proxy = requestMime (Proxy :: Proxy c) methodPost [204]
361+ clientBuild Proxy = clientEmpty methodPost [204]
309instance MimeUnrender c a => GoogleClient (Put (c ': cs) a) where
363instance MimeUnrender c a => GoogleClient (Put (c ': cs) a) where
310 type Fn (Put (c ': cs) a) = Client a
364 type Fn (Put (c ': cs) a) = Client a
312− requestBuild Proxy = requestMime (Proxy :: Proxy c) methodPut [200, 201]
366+ clientBuild Proxy = clientMime (Proxy :: Proxy c) methodPut [200, 201]
314instance GoogleClient (Put (c ': cs) ()) where
368instance GoogleClient (Put (c ': cs) ()) where
315 type Fn (Put (c ': cs) ()) = Client ()
369 type Fn (Put (c ': cs) ()) = Client ()
317− requestBuild Proxy = requestMime (Proxy :: Proxy c) methodPut [204]
371+ clientBuild Proxy = clientEmpty methodPut [204]
319instance MimeUnrender c a => GoogleClient (Patch (c ': cs) a) where
373instance MimeUnrender c a => GoogleClient (Patch (c ': cs) a) where
320 type Fn (Patch (c ': cs) a) = Client a
374 type Fn (Patch (c ': cs) a) = Client a
322− requestBuild Proxy = requestMime (Proxy :: Proxy c) methodPatch [200, 201]
376+ clientBuild Proxy = clientMime (Proxy :: Proxy c) methodPatch [200, 201]
324instance GoogleClient (Patch (c ': cs) ()) where
378instance GoogleClient (Patch (c ': cs) ()) where
325 type Fn (Patch (c ': cs) ()) = Client ()
379 type Fn (Patch (c ': cs) ()) = Client ()
327− requestBuild Proxy = requestMime (Proxy :: Proxy c) methodPatch [204]
381+ clientBuild Proxy = clientEmpty methodPatch [204]
329−type Client a = ReaderT Manager IO (Either Error a)
383+-- perform :: Accept
330−
384+-- -> (ResponseBody -> Client (Either SerializeError a))
331−requestMime :: MimeUnrender c a
333− -> Method
385+-- -> Method
334− -> [Int]
386+-- -> [Int]
335− -> Request
387+-- -> RequestBuilder
336− -> Client a
388+-- -> Client a
337−requestMime p = perform c go
343− case mimeUnrender c lbs of
344− Left e -> SerializeError
345− Right x -> undefined
389+-- perform a meth ns f rq = undefined -- do
347−requestbody :: Accept c
352− -> Client ResponseBody
353−requestBody p = perform c (pure . Right)
357−-- performNoBody = httpNoBody
360− -> (ResponseBody -> Client (Either SerializeError a))
365−perform a meth ns f rq = do
366− m <- ask
390+ -- m <- ask
367− x <- lift . try $ Client.http m (clientRequest meth a rq)
391+ -- x <- lift . try $ Client.http m (clientRequest meth a rq)
368− either (failure . TransportError) success x
392+ -- either (failure . TransportError) success x
370− failure = pure . Left
394+ -- failure = pure . Left
372− success rs = do
396+ -- success rs = do
373− let s = Client.responseStatus rs
397+ -- let s = Client.responseStatus rs
374− b = Client.responseBody rs
398+ -- b = Client.responseBody rs
375− hs = Client.responseHeaders rs
399+ -- hs = Client.responseHeaders rs
376− case content hs of
400+ -- case content hs of
377− unless (matches respCT (acceptCT)) $
401+ -- unless (matches respCT (acceptCT)) $
378− left $ UnsupportedContentType respCT respBody
402+ -- left $ UnsupportedContentType respCT respBody
380− Nothing -> failure (SerializeError' (SerializeError hs))
404+ -- Nothing -> failure (SerializeError' (SerializeError hs))
382− Just _ | statusInvalid s -> do
406+ -- Just _ | statusInvalid s -> do
383− lbs <- sinkLBS b
407+ -- lbs <- sinkLBS b
384− failure (ServiceError' (ServiceError s c hs lbs))
408+ -- failure (ServiceError' (ServiceError s c hs lbs))
386− Just c | contentInvalid c -> do
410+ -- Just c | contentInvalid c -> do
387− lbs <- sinkLBS b
411+ -- lbs <- sinkLBS b
388− failure (SerializeError' (SerializeError s c hs lbs))
412+ -- failure (SerializeError' (SerializeError s c hs lbs))
391− Just c -> first SerializeError' <$> f b
415+ -- Just c -> first SerializeError' <$> f b
393− content :: [Header] -> Maybe c
417+ -- content :: [Header] -> Maybe c
394− content = parseAccept
418+ -- content = parseAccept
395− . fromMaybe "application/octet-stream"
419+ -- . fromMaybe "application/octet-stream"
396− . lookup hContentType
420+ -- . lookup hContentType
398− statusCheck s = fromEnum s `elem` ns
422+ -- statusCheck s = fromEnum s `elem` ns
399− contentCheck = matches a
423+ -- contentCheck = matches a
401−clientRequest :: Method -> MediaType -> Request -> ClientRequest
402−clientRequest m c Request {..} = def
404− , Client.checkStatus = \_ _ _ -> Nothing
407−sinkLBS :: ResponseBody -> Client LBS.ByteString
408−sinkLBS = fmap LBS.fromChunks . ($$+- CL.consme)
410buildText :: ToText a => a -> Builder
425buildText :: ToText a => a -> Builder
411buildText = Build.fromText . toText
426buildText = Build.fromText . toText