Skip to content

Commit 4fa31a8

Browse files
committed
Add Lenient mode for MultipartForm
Some users may want to process parsing errors in their handlers instead of relying on servant-multipart's default error response. So we introduce Lenient mode similar to Capture.
1 parent 783cff5 commit 4fa31a8

File tree

1 file changed

+21
-12
lines changed

1 file changed

+21
-12
lines changed

src/Servant/Multipart.hs

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
{-# LANGUAGE CPP #-}
2+
{-# LANGUAGE AllowAmbiguousTypes #-}
23
{-# LANGUAGE DataKinds #-}
34
{-# LANGUAGE TypeFamilies #-}
45
{-# LANGUAGE InstanceSigs #-}
@@ -19,6 +20,7 @@
1920
-- an API. See haddocks of 'MultipartForm' for an introduction.
2021
module Servant.Multipart
2122
( MultipartForm
23+
, MultipartForm'
2224
, MultipartData(..)
2325
, FromMultipart(..)
2426
, lookupInput
@@ -57,6 +59,7 @@ import Network.HTTP.Media.MediaType ((//), (/:))
5759
import Network.Wai
5860
import Network.Wai.Parse
5961
import Servant
62+
import Servant.API.Modifiers (FoldLenient)
6063
import Servant.Client.Core (HasClient(..), RequestBody(RequestBodySource), setRequestBody)
6164
import Servant.Docs
6265
import Servant.Foreign
@@ -151,7 +154,10 @@ import qualified Data.ByteString.Lazy as LBS
151154
-- after your handler has run, if they are still there. It is
152155
-- therefore recommended to move or copy them somewhere in your
153156
-- handler code if you need to keep the content around.
154-
data MultipartForm tag a
157+
type MultipartForm tag a = MultipartForm' '[] tag a
158+
159+
-- | 'MultipartForm' which can be modified with 'Servant.API.Modifiers.Lenient'.
160+
data MultipartForm' (mods :: [*]) tag a
155161

156162
-- | What servant gets out of a @multipart/form-data@ form submission.
157163
--
@@ -288,11 +294,12 @@ instance ToMultipart tag (MultipartData tag) where
288294
instance ( FromMultipart tag a
289295
, MultipartBackend tag
290296
, LookupContext config (MultipartOptions tag)
297+
, SBoolI (FoldLenient mods)
291298
, HasServer sublayout config )
292-
=> HasServer (MultipartForm tag a :> sublayout) config where
299+
=> HasServer (MultipartForm' mods tag a :> sublayout) config where
293300

294-
type ServerT (MultipartForm tag a :> sublayout) m =
295-
a -> ServerT sublayout m
301+
type ServerT (MultipartForm' mods tag a :> sublayout) m =
302+
If (FoldLenient mods) (Either String a) a -> ServerT sublayout m
296303

297304
#if MIN_VERSION_servant_server(0,12,0)
298305
hoistServerWithContext _ pc nt s = hoistServerWithContext (Proxy :: Proxy sublayout) pc nt . s
@@ -306,16 +313,16 @@ instance ( FromMultipart tag a
306313
popts = Proxy :: Proxy (MultipartOptions tag)
307314
multipartOpts = fromMaybe (defaultMultipartOptions pbak)
308315
$ lookupContext popts config
309-
subserver' = addMultipartHandling pbak multipartOpts subserver
316+
subserver' = addMultipartHandling @tag @a @mods pbak multipartOpts subserver
310317

311318
-- | Upon seeing @MultipartForm a :> ...@ in an API type,
312319
-- servant-client will take a parameter of type @(LBS.ByteString, a)@,
313320
-- where the bytestring is the boundary to use (see 'genBoundary'), and
314321
-- replace the request body with the contents of the form.
315322
instance (ToMultipart tag a, HasClient m api, MultipartBackend tag)
316-
=> HasClient m (MultipartForm tag a :> api) where
323+
=> HasClient m (MultipartForm' mods tag a :> api) where
317324

318-
type Client m (MultipartForm tag a :> api) =
325+
type Client m (MultipartForm' mods tag a :> api) =
319326
(LBS.ByteString, a) -> Client m api
320327

321328
clientWithRoute pm _ req (boundary, param) =
@@ -427,10 +434,11 @@ check pTag tag = withRequest $ \request -> do
427434
where parseOpts = generalOptions tag
428435

429436
-- Add multipart extraction support to a Delayed.
430-
addMultipartHandling :: forall tag multipart env a. (FromMultipart tag multipart, MultipartBackend tag)
437+
addMultipartHandling :: forall tag multipart (mods :: [*]) env a. (FromMultipart tag multipart, MultipartBackend tag)
438+
=> SBoolI (FoldLenient mods)
431439
=> Proxy tag
432440
-> MultipartOptions tag
433-
-> Delayed env (multipart -> a)
441+
-> Delayed env (If (FoldLenient mods) (Either String multipart) multipart -> a)
434442
-> Delayed env a
435443
addMultipartHandling pTag opts subserver =
436444
addBodyCheck subserver contentCheck bodyCheck
@@ -440,10 +448,11 @@ addMultipartHandling pTag opts subserver =
440448

441449
bodyCheck () = do
442450
mpd <- check pTag opts :: DelayedIO (MultipartData tag)
443-
case fromMultipart mpd of
444-
Left msg -> liftRouteResult $ FailFatal
451+
case (sbool :: SBool (FoldLenient mods), fromMultipart @tag @multipart mpd) of
452+
(SFalse, Left msg) -> liftRouteResult $ FailFatal
445453
err400 { errBody = "Could not decode multipart mime body: " <> cs msg }
446-
Right x -> return x
454+
(SFalse, Right x) -> return x
455+
(STrue, res) -> return $ either (Left . cs) Right res
447456

448457
contentTypeH req = fromMaybe "application/octet-stream" $
449458
lookup "Content-Type" (requestHeaders req)

0 commit comments

Comments
 (0)