on develop help sign in
Exposing multipart non-root object's Content-Type
commit072be1ac933ce3c7623d7f2109096c1f74621e8d authorBrendan Hay <brendan.g.hay@gmail.com> dateFri 10 Jun 2016, 16:15 (10 years ago) parent9debfe3
8 files changed+100−79
core/src/Network/Google/Types.hs+35−41 gen/src/Gen/Syntax.hs+23−11 gogol/gogol.cabal+1 gogol/src/Network/Google.hs+2−1 gogol/src/Network/Google/Internal/Body.hs+22−4 gogol/src/Network/Google/Internal/HTTP.hs+8−9 gogol/src/Network/Google/Internal/Logger.hs+2−1 gogol/src/Network/Google/Internal/Multipart.hs+7−12
core/src/Network/Google/Types.hs+35−41
@@ -56,14 +56,12 @@ import qualified Network.HTTP.Types as HTTP
5656import Servant.API
5757import Web.HttpApiData
5858
59−data AltJSON = AltJSON deriving (Eq, Ord, Show, Read, Generic, Typeable)
60−data AltMedia = AltMedia deriving (Eq, Ord, Show, Read, Generic, Typeable)
61−
62−instance ToHttpApiData AltJSON where toQueryParam = const "json"
63−instance ToHttpApiData AltMedia where toQueryParam = const "media"
64−
59+data AltJSON = AltJSON deriving (Eq, Ord, Show, Read, Generic, Typeable)
60+data AltMedia = AltMedia deriving (Eq, Ord, Show, Read, Generic, Typeable)
6561data Multipart = Multipart deriving (Eq, Ord, Show, Read, Generic, Typeable)
6662
63+instance ToHttpApiData AltJSON where toQueryParam = const "json"
64+instance ToHttpApiData AltMedia where toQueryParam = const "media"
6765instance ToHttpApiData Multipart where toQueryParam = const "multipart"
6866
6967newtype OAuthScope = OAuthScope Text
@@ -157,7 +155,7 @@ newtype ServiceId = ServiceId Text
157155 )
158156
159157newtype MediaDownload a = MediaDownload a
160−data MediaUpload a = MediaUpload a Part
158+data MediaUpload a = MediaUpload a Body
161159
162160_Coerce :: (Coercible a b, Coercible b a) => Iso' a b
163161_Coerce = iso coerce coerce
@@ -265,28 +263,27 @@ serviceSecure = lens _svcSecure (\s a -> s { _svcSecure = a })
265263serviceTimeout :: Lens' ServiceConfig (Maybe Seconds)
266264serviceTimeout = lens _svcTimeout (\s a -> s { _svcTimeout = a })
267265
268−-- | A single part of a multipart message.
269−data Part = Part MediaType [(HeaderName, ByteString)] RequestBody
266+-- | A single part of a (potentially multipart) request body.
267+data Body = Body !MediaType !RequestBody
270268
271−data Payload
272− = Body !MediaType !RequestBody
273− | Related ![Part]
269+setMIMEType :: MediaType -> Body -> Body
270+setMIMEType m (Body _ b) = Body m b
274271
275272-- | An intermediary request builder.
276273data Request = Request
277274 { _rqPath :: !Builder
278275 , _rqQuery :: !(DList (ByteString, Maybe ByteString))
279276 , _rqHeaders :: !(DList (HeaderName, ByteString))
280− , _rqBody :: !(Maybe Payload)
277+ , _rqBody :: ![Body]
281278 }
282279
283280instance Monoid Request where
284− mempty = Request mempty mempty mempty Nothing
281+ mempty = Request mempty mempty mempty mempty
285282 mappend a b = Request
286− (_rqPath a <> "/" <> _rqPath b)
287− (_rqQuery a <> _rqQuery b)
288− (_rqHeaders a <> _rqHeaders b)
289− (_rqBody b <|> _rqBody a)
283+ (_rqPath a <> "/" <> _rqPath b)
284+ (_rqQuery a <> _rqQuery b)
285+ (_rqHeaders a <> _rqHeaders b)
286+ (_rqBody b <> _rqBody a)
290287
291288appendPath :: Request -> Builder -> Request
292289appendPath rq x = rq { _rqPath = _rqPath rq <> "/" <> x }
@@ -305,11 +302,8 @@ appendHeader rq k (Just v) = rq
305302 { _rqHeaders = DList.snoc (_rqHeaders rq) (k, Text.encodeUtf8 v)
306303 }
307304
308−setBody :: Request -> MediaType -> RequestBody -> Request
309−setBody rq c x = rq { _rqBody = Just (Body c x) }
310−
311−setRelated :: Request -> [Part] -> Request
312−setRelated rq ps = rq { _rqBody = Just (Related ps) }
305+setBody :: Request -> [Body] -> Request
306+setBody rq bs = rq { _rqBody = bs }
313307
314308-- | A materialised 'http-client' request and associated response parser.
315309data Client a = Client
@@ -357,25 +351,22 @@ client f cs m ns rq s = Client
357351 }
358352
359353class Accept c => ToBody c a where
360− toBody :: Proxy c -> a -> RequestBody
361−
362−instance ToBody OctetStream RequestBody where
363− toBody Proxy = id
354+ toBody :: Proxy c -> a -> Body
364355
365356instance ToBody OctetStream ByteString where
366− toBody Proxy = RequestBodyBS
357+ toBody p = Body (contentType p) . RequestBodyBS
367358
368359instance ToBody OctetStream LBS.ByteString where
369− toBody Proxy = RequestBodyLBS
360+ toBody p = Body (contentType p) . RequestBodyLBS
370361
371362instance ToBody PlainText ByteString where
372− toBody Proxy = RequestBodyBS
363+ toBody p = Body (contentType p) . RequestBodyBS
373364
374365instance ToBody PlainText LBS.ByteString where
375− toBody Proxy = RequestBodyLBS
366+ toBody p = Body (contentType p) . RequestBodyLBS
376367
377368instance ToJSON a => ToBody JSON a where
378− toBody Proxy = RequestBodyLBS . encode
369+ toBody p = Body (contentType p) . RequestBodyLBS . encode
379370
380371class Accept c => FromStream c a where
381372 fromStream :: Proxy c
@@ -417,13 +408,18 @@ data MultipartRelated (cs :: [*]) m
417408instance ( ToBody c m
418409 , GoogleClient fn
419410 ) => GoogleClient (MultipartRelated (c ': cs) m :> fn) where
420− type Fn (MultipartRelated (c ': cs) m :> fn) = m -> Part -> Fn fn
411+ type Fn (MultipartRelated (c ': cs) m :> fn) = m -> Body -> Fn fn
421412
422413 buildClient Proxy rq m b =
423414 buildClient (Proxy :: Proxy fn) $
424− setRelated rq [Part (contentType mc) [] (toBody mc m), b]
425− where
426− mc = Proxy :: Proxy c
415+ setBody rq [toBody (Proxy :: Proxy c) m, b]
416+
417+instance GoogleClient fn => GoogleClient (AltMedia :> fn) where
418+ type Fn (AltMedia :> fn) = Body -> Fn fn
419+
420+ buildClient Proxy rq b =
421+ buildClient (Proxy :: Proxy fn) $
422+ setBody rq [b]
427423
428424instance (KnownSymbol s, GoogleClient fn) => GoogleClient (s :> fn) where
429425 type Fn (s :> fn) = Fn fn
@@ -513,11 +509,9 @@ instance ( ToBody c a
513509 ) => GoogleClient (ReqBody (c ': cs) a :> fn) where
514510 type Fn (ReqBody (c ': cs) a :> fn) = a -> Fn fn
515511
516− buildClient Proxy rq = buildClient (Proxy :: Proxy fn)
517− . setBody rq (contentType p)
518− . toBody p
519− where
520− p = Proxy :: Proxy c
512+ buildClient Proxy rq x =
513+ buildClient (Proxy :: Proxy fn) $
514+ setBody rq [toBody (Proxy :: Proxy c) x]
521515
522516instance {-# OVERLAPPABLE #-}
523517 FromStream c a => GoogleClient (Get (c ': cs) a) where
gen/src/Gen/Syntax.hs+23−11
@@ -87,7 +87,7 @@ downloadAlias m
8787 | _mSupportsMediaDownload m = Just download
8888 | otherwise = Nothing
8989 where
90− download = servantSub (downloadVerb m) (params ++ [altParam])
90+ download = servantSub (downloadVerb m) (params ++ [downloadParam])
9191
9292 params = requestQuery m . Map.delete "alt" $ requestQueryParams m
9393
@@ -96,7 +96,7 @@ uploadAlias sub m
9696 | _mSupportsMediaUpload m = Just upload
9797 | otherwise = Nothing
9898 where
99− upload = servantSub (jsonVerb m) (path ++ params ++ [uploadParam, media])
99+ upload = servantSub (jsonVerb m) (path ++ params ++ media)
100100
101101 params = requestQuery m (requestQueryParams m)
102102
@@ -106,12 +106,15 @@ uploadAlias sub m
106106
107107 media = case _mRequest m of
108108 Just b ->
109− TyApp (TyApp (TyCon "MultipartRelated") jsonMedia)
110− (tycon (ref b))
109+ [ multipartParam
110+ , TyApp (TyApp (TyCon "MultipartRelated") jsonMedia)
111+ (tycon (ref b))
112+ ]
111113
112114 Nothing ->
113− TyApp (TyApp (TyCon "ReqBody") streamMedia)
114− (TyCon "RequestBody")
115+ [ mediaParam
116+ , TyCon "AltMedia"
117+ ]
115118
116119requestPath :: Method Solved -> Text -> [Type]
117120requestPath m = map go . extractPath
@@ -176,13 +179,18 @@ jsonMedia, streamMedia :: Type
176179jsonMedia = tylist ["JSON"]
177180streamMedia = tylist ["OctetStream"]
178181
179−altParam :: Type
180−altParam =
182+downloadParam :: Type
183+downloadParam =
181184 TyApp (TyApp (TyCon "QueryParam") (sing "alt"))
182185 (TyCon "AltMedia")
183186
184−uploadParam :: Type
185−uploadParam =
187+mediaParam :: Type
188+mediaParam =
189+ TyApp (TyApp (TyCon "QueryParam") (sing "uploadType"))
190+ (TyCon "AltMedia")
191+
192+multipartParam :: Type
193+multipartParam =
186194 TyApp (TyApp (TyCon "QueryParam") (sing "uploadType"))
187195 (TyCon "Multipart")
188196
@@ -257,7 +265,11 @@ uploadDecl n pre api url fs m =
257265
258266 extras = maybeToList alt ++ [upl] ++ payload ++ [var media]
259267 where
260− upl = app (var "Just") (var "Multipart")
268+ upl = app (var "Just") $
269+ if isJust (_mRequest m)
270+ then var "Multipart"
271+ else var "AltMedia"
272+
261273 alt = app (var "Just") . var . name . Text.unpack . alternate <$>
262274 (Map.lookup "alt" (_mParameters m) >>= view iDefault)
263275
gogol/gogol.cabal+1
@@ -75,6 +75,7 @@ library
7575 , http-types >= 0.8.6
7676 , lens >= 4.4
7777 , memory >= 0.8
78+ , mime-types >= 0.1
7879 , monad-control >= 1
7980 , mtl >= 2.1.3.1
8081 , resourcet >= 1.1
gogol/src/Network/Google.hs+2−1
@@ -135,6 +135,7 @@ import Network.Google.Internal.Logger
135135import Network.Google.Prelude
136136import Network.Google.Types
137137import Network.HTTP.Conduit (newManager, tlsManagerSettings)
138+import Network.HTTP.Media.MediaType (MediaType)
138139
139140newtype Google s a = Google { unGoogle :: ReaderT (Env s) (ResourceT IO) a }
140141 deriving
@@ -242,7 +243,7 @@ upload :: ( MonadGoogle s m
242243 , GoogleRequest (MediaUpload a)
243244 )
244245 => a
245− -> RequestBody
246+ -> Body
246247 -> m (Rs (MediaUpload a))
247248upload x = send . MediaUpload x
248249
gogol/src/Network/Google/Internal/Body.hs+22−4
@@ -1,3 +1,5 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+
13-- |
24-- Module : Network.Google.Internal.Body
35-- Copyright : (c) 2015-2016 Brendan Hay
@@ -10,17 +12,33 @@ module Network.Google.Internal.Body where
1012
1113import Control.Monad.IO.Class (MonadIO (..))
1214import Data.Conduit.Binary (sourceFile)
15+import Data.Maybe (fromMaybe)
16+import qualified Data.Text as Text
17+import Network.Google.Types (Body (..))
1318import Network.HTTP.Conduit (RequestBody, requestBodySource)
19+import Network.HTTP.Media (MediaType, parseAccept, (//))
20+import qualified Network.Mime as MIME
1421import System.IO
1522
1623-- | Convenience function for obtaining the size of a file.
1724getFileSize :: MonadIO m => FilePath -> m Integer
1825getFileSize f = liftIO (withBinaryFile f ReadMode hFileSize)
1926
20−-- | Construct a 'RequestBody' from a 'FilePath'.
27+getMIMEType :: FilePath -> MediaType
28+getMIMEType =
29+ fromMaybe ("application" // "octet-stream")
30+ . parseAccept
31+ . MIME.defaultMimeLookup
32+ . Text.takeWhileEnd (/= '/')
33+ . Text.pack
34+
35+-- | Construct a 'Body' from a 'FilePath'.
2136--
22−-- /See:/ 'ToBody'.
23−sourceBody :: MonadIO m => FilePath -> m RequestBody
37+-- This attempts to guess the MIME type from the file extension,
38+-- you can use "Network.Google.Types.setMIMEType" to set a MIME type explicitly.
39+sourceBody :: MonadIO m => FilePath -> m Body
2440sourceBody f = do
2541 n <- getFileSize f
26− pure $ requestBodySource (fromIntegral n) (sourceFile f)
42+ pure $ Body
43+ (getMIMEType f)
44+ (requestBodySource (fromIntegral n) (sourceFile f))
gogol/src/Network/Google/Internal/HTTP.hs+8−9
@@ -123,13 +123,12 @@ perform Env{..} x = catches go handlers
123123 err e = return (Left e)
124124
125125getContent :: MonadIO m
126− => Maybe Payload
126+ => [Body]
127127 -> m ([Header] -> [Header], RequestBody)
128−getContent = \case
129− Nothing -> pure (id, mempty)
130− Just (Body t s) -> pure (((hContentType, renderHeader t) :), s)
131− Just (Related ps) -> do
132− b <- genBoundary
133− pure ( (multipartHeader b :)
134− , renderParts b ps
135− )
128+getContent [] = pure (id, mempty)
129+getContent [Body t s] = pure (((hContentType, renderHeader t) :), s)
130+getContent bs = do
131+ b <- genBoundary
132+ pure ( (multipartHeader b :)
133+ , renderParts b bs
134+ )
gogol/src/Network/Google/Internal/Logger.hs+2−1
@@ -147,7 +147,6 @@ instance ToLog RequestBody where
147147 build = \case
148148 RequestBodyBuilder n _ -> " <msger:" <> build n <> ">"
149149 RequestBodyStream n _ -> " <stream:" <> build n <> ">"
150− RequestBodyStreamChunked _ -> " <chunked>"
151150
152151 RequestBodyLBS lbs
153152 | n <= 4096 -> build lbs
@@ -161,6 +160,8 @@ instance ToLog RequestBody where
161160 where
162161 n = BS.length bs
163162
163+ _ -> " <chunked>"
164+
164165instance ToLog HttpException where
165166 build x = "[HttpException] {\n" <> build (show x) <> "\n}"
166167
gogol/src/Network/Google/Internal/Multipart.hs+7−12
@@ -1,4 +1,4 @@
1− {-# LANGUAGE OverloadedStrings #-}
1+{-# LANGUAGE OverloadedStrings #-}
22
33-- |
44-- Module : Network.Google.Internal.Multipart
@@ -12,14 +12,14 @@ module Network.Google.Internal.Multipart where
1212
1313import Control.Monad.IO.Class (MonadIO (..))
1414import Data.ByteString (ByteString)
15−import qualified Data.ByteString as BS
15+import qualified Data.ByteString as BS
1616import Data.ByteString.Builder.Extra (byteStringCopy)
17−import qualified Data.CaseInsensitive as CI
17+import qualified Data.CaseInsensitive as CI
1818import Data.Monoid ((<>))
19−import Network.Google.Types (Part (..))
19+import Network.Google.Types (Body (..))
2020import Network.HTTP.Client
2121import Network.HTTP.Client.MultipartFormData (webkitBoundary)
22−import Network.HTTP.Media (RenderHeader(..))
22+import Network.HTTP.Media (RenderHeader (..))
2323import Network.HTTP.Types (Header, hContentType)
2424
2525-- POST /upload/drive/v2/files?uploadType=multipart HTTP/1.1
@@ -58,18 +58,13 @@ part (Boundary bs) = copy "--" <> copy bs <> copy "--\r\n"
5858copy :: ByteString -> RequestBody
5959copy bs = RequestBodyBuilder (fromIntegral (BS.length bs)) (byteStringCopy bs)
6060
61−renderParts :: Boundary -> [Part] -> RequestBody
61+renderParts :: Boundary -> [Body] -> RequestBody
6262renderParts b = (<> part b) . foldMap go
6363 where
64− go (Part ct hs x) =
64+ go (Body ct x) =
6565 start b
6666 <> copy "Content-Type: "
6767 <> copy (renderHeader ct)
68− <> foldMap (\(k, v) ->
69− copy "\r\n"
70− <> copy (CI.original k)
71− <> copy ": "
72− <> copy v) hs
7368 <> copy "\r\n\r\n"
7469 <> x
7570 <> copy "\r\n"