Skip to content

Commit b415dc9

Browse files
committed
Add HasDocs instance for MultipartForm.
1 parent 3a0b650 commit b415dc9

File tree

2 files changed

+66
-1
lines changed

2 files changed

+66
-1
lines changed

servant-multipart.cabal

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,10 @@ library
2626
bytestring >= 0.10 && <0.11,
2727
directory,
2828
http-media >= 0.6 && <0.8,
29+
lens >= 4.0 && < 4.16,
2930
resourcet >=1.1 && <1.2,
3031
servant >=0.10 && <0.12,
32+
servant-docs >=0.10 && <0.12,
3133
servant-server >=0.10 && <0.12,
3234
text >=1.2 && <1.3,
3335
transformers >=0.3 && <0.6,

src/Servant/Multipart.hs

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{-# LANGUAGE DataKinds #-}
22
{-# LANGUAGE TypeFamilies #-}
3+
{-# LANGUAGE InstanceSigs #-}
34
{-# LANGUAGE TypeOperators #-}
45
{-# LANGUAGE RecordWildCards #-}
56
{-# LANGUAGE FlexibleContexts #-}
@@ -27,22 +28,27 @@ module Servant.Multipart
2728
, defaultTmpBackendOptions
2829
, Input(..)
2930
, FileData(..)
31+
-- * servant-docs
32+
, ToMultipartSample(..)
3033
) where
3134

35+
import Control.Lens ((<>~), view)
3236
import Control.Monad
3337
import Control.Monad.IO.Class
3438
import Control.Monad.Trans.Resource
3539
import Data.ByteString.Lazy (ByteString)
3640
import Data.Function
3741
import Data.List (find)
3842
import Data.Maybe
39-
import Data.Text (Text)
43+
import Data.Semigroup
44+
import Data.Text (Text, unpack)
4045
import Data.Text.Encoding (decodeUtf8)
4146
import Data.Typeable
4247
import Network.HTTP.Media ((//))
4348
import Network.Wai
4449
import Network.Wai.Parse
4550
import Servant
51+
import Servant.Docs
4652
import Servant.Server.Internal
4753
import System.Directory
4854
import System.IO
@@ -418,3 +424,60 @@ instance {-# OVERLAPPING #-}
418424
instance HasLink sub => HasLink (MultipartForm a :> sub) where
419425
type MkLink (MultipartForm a :> sub) = MkLink sub
420426
toLink _ = toLink (Proxy :: Proxy sub)
427+
428+
class ToMultipartSample tag a where
429+
toMultipartSamples :: Proxy a -> [(Text, MultipartData tag)]
430+
431+
multipartInputToItem :: Input -> Text
432+
multipartInputToItem (Input name val) =
433+
" - *" <> name <> "*: " <> "`" <> val <> "`"
434+
435+
multipartFileToItem :: FileData tag -> Text
436+
multipartFileToItem (FileData name _ contentType _) =
437+
" - *" <> name <> "*, content-type: " <> "`" <> contentType <> "`"
438+
439+
multipartSampleToDesc :: (Text, MultipartData tag) -> Text
440+
multipartSampleToDesc (desc, MultipartData inputs files) =
441+
"- " <> desc <> "\n" <>
442+
" - textual inputs (any `<input>` type but file):\n" <>
443+
foldMap (\input -> multipartInputToItem input <> "\n") inputs <>
444+
" - file inputs (any HTML input that looks like `<input type=\"file\" name=\"somefile\" />`):\n" <>
445+
foldMap (\file -> multipartFileToItem file <> "\n") files
446+
447+
toMultipartDescriptions
448+
:: forall tag a.
449+
ToMultipartSample tag a
450+
=> Proxy tag -> Proxy a -> [Text]
451+
toMultipartDescriptions _ proxyA = fmap multipartSampleToDesc samples
452+
where
453+
samples :: [(Text, MultipartData tag)]
454+
samples = toMultipartSamples proxyA
455+
456+
toMultipartNotes
457+
:: ToMultipartSample tag a
458+
=> Int -> Proxy tag -> Proxy a -> DocNote
459+
toMultipartNotes maxSamples' proxyTag proxyA =
460+
let sampleLines = take maxSamples' $ toMultipartDescriptions proxyTag proxyA
461+
body =
462+
[ "This endpoint takes `multipart/form-data` requests. The following is " <>
463+
"a list of sample requests:"
464+
, foldMap (<> "\n") sampleLines
465+
]
466+
in DocNote "Multipart Request Samples" $ fmap unpack body
467+
468+
instance (HasDocs api, ToMultipartSample tag a) => HasDocs (MultipartForm tag a :> api) where
469+
docsFor
470+
:: Proxy (MultipartForm tag a :> api)
471+
-> (Endpoint, Action)
472+
-> DocOptions
473+
-> API
474+
docsFor _ (endpoint, action) opts =
475+
let newAction =
476+
action
477+
& notes <>~
478+
[ toMultipartNotes
479+
(view maxSamples opts)
480+
(Proxy :: Proxy tag)
481+
(Proxy :: Proxy a)
482+
]
483+
in docsFor (Proxy :: Proxy api) (endpoint, newAction) opts

0 commit comments

Comments
 (0)