|
1 | 1 | {-# LANGUAGE DataKinds #-}
|
2 | 2 | {-# LANGUAGE TypeFamilies #-}
|
| 3 | +{-# LANGUAGE InstanceSigs #-} |
3 | 4 | {-# LANGUAGE TypeOperators #-}
|
4 | 5 | {-# LANGUAGE RecordWildCards #-}
|
5 | 6 | {-# LANGUAGE FlexibleContexts #-}
|
@@ -27,22 +28,27 @@ module Servant.Multipart
|
27 | 28 | , defaultTmpBackendOptions
|
28 | 29 | , Input(..)
|
29 | 30 | , FileData(..)
|
| 31 | + -- * servant-docs |
| 32 | + , ToMultipartSample(..) |
30 | 33 | ) where
|
31 | 34 |
|
| 35 | +import Control.Lens ((<>~), view) |
32 | 36 | import Control.Monad
|
33 | 37 | import Control.Monad.IO.Class
|
34 | 38 | import Control.Monad.Trans.Resource
|
35 | 39 | import Data.ByteString.Lazy (ByteString)
|
36 | 40 | import Data.Function
|
37 | 41 | import Data.List (find)
|
38 | 42 | import Data.Maybe
|
39 |
| -import Data.Text (Text) |
| 43 | +import Data.Semigroup |
| 44 | +import Data.Text (Text, unpack) |
40 | 45 | import Data.Text.Encoding (decodeUtf8)
|
41 | 46 | import Data.Typeable
|
42 | 47 | import Network.HTTP.Media ((//))
|
43 | 48 | import Network.Wai
|
44 | 49 | import Network.Wai.Parse
|
45 | 50 | import Servant
|
| 51 | +import Servant.Docs |
46 | 52 | import Servant.Server.Internal
|
47 | 53 | import System.Directory
|
48 | 54 | import System.IO
|
@@ -418,3 +424,60 @@ instance {-# OVERLAPPING #-}
|
418 | 424 | instance HasLink sub => HasLink (MultipartForm a :> sub) where
|
419 | 425 | type MkLink (MultipartForm a :> sub) = MkLink sub
|
420 | 426 | 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