Skip to content

Commit 77ecd86

Browse files
authored
Merge pull request #11 from cdepillabout/add-hasdocs-instance
Add HasDocs instance
2 parents c466d33 + 243c9b1 commit 77ecd86

File tree

4 files changed

+170
-1
lines changed

4 files changed

+170
-1
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
dist/
22
dist-newstyle/
3+
.stack-work/

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: 101 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,28 @@ 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)
40+
import Data.Foldable (foldMap)
3641
import Data.Function
3742
import Data.List (find)
3843
import Data.Maybe
39-
import Data.Text (Text)
44+
import Data.Monoid
45+
import Data.Text (Text, unpack)
4046
import Data.Text.Encoding (decodeUtf8)
4147
import Data.Typeable
4248
import Network.HTTP.Media ((//))
4349
import Network.Wai
4450
import Network.Wai.Parse
4551
import Servant
52+
import Servant.Docs
4653
import Servant.Server.Internal
4754
import System.Directory
4855
import System.IO
@@ -418,3 +425,96 @@ instance {-# OVERLAPPING #-}
418425
instance HasLink sub => HasLink (MultipartForm a :> sub) where
419426
type MkLink (MultipartForm a :> sub) = MkLink sub
420427
toLink _ = toLink (Proxy :: Proxy sub)
428+
429+
-- | The 'ToMultipartSample' class allows you to create sample 'MultipartData'
430+
-- inputs for your type for use with "Servant.Docs". This is used by the
431+
-- 'HasDocs' instance for 'MultipartForm'.
432+
--
433+
-- Given the example 'User' type and 'FromMultipart' instance above, here is a
434+
-- corresponding 'ToMultipartSample' instance:
435+
--
436+
-- @
437+
-- data User = User { username :: Text, pic :: FilePath }
438+
--
439+
-- instance 'ToMultipartSample' 'Tmp' User where
440+
-- 'toMultipartSamples' proxy =
441+
-- [ ( \"sample 1\"
442+
-- , 'MultipartData'
443+
-- [ 'Input' \"username\" \"Elvis Presley\" ]
444+
-- [ 'FileData'
445+
-- \"pic\"
446+
-- \"playing_guitar.jpeg\"
447+
-- \"image/jpeg\"
448+
-- \"/tmp/servant-multipart000.buf\"
449+
-- ]
450+
-- )
451+
-- ]
452+
-- @
453+
class ToMultipartSample tag a where
454+
toMultipartSamples :: Proxy a -> [(Text, MultipartData tag)]
455+
456+
-- | Format an 'Input' into a markdown list item.
457+
multipartInputToItem :: Input -> Text
458+
multipartInputToItem (Input name val) =
459+
" - *" <> name <> "*: " <> "`" <> val <> "`"
460+
461+
-- | Format a 'FileData' into a markdown list item.
462+
multipartFileToItem :: FileData tag -> Text
463+
multipartFileToItem (FileData name _ contentType _) =
464+
" - *" <> name <> "*, content-type: " <> "`" <> contentType <> "`"
465+
466+
-- | Format a description and a sample 'MultipartData' into a markdown list
467+
-- item.
468+
multipartSampleToDesc
469+
:: Text -- ^ The description for the sample.
470+
-> MultipartData tag -- ^ The sample 'MultipartData'.
471+
-> Text -- ^ A markdown list item.
472+
multipartSampleToDesc desc (MultipartData inputs files) =
473+
"- " <> desc <> "\n" <>
474+
" - textual inputs (any `<input>` type but file):\n" <>
475+
foldMap (\input -> multipartInputToItem input <> "\n") inputs <>
476+
" - file inputs (any HTML input that looks like `<input type=\"file\" name=\"somefile\" />`):\n" <>
477+
foldMap (\file -> multipartFileToItem file <> "\n") files
478+
479+
-- | Format a list of samples generated with 'ToMultipartSample' into sections
480+
-- of markdown.
481+
toMultipartDescriptions
482+
:: forall tag a.
483+
ToMultipartSample tag a
484+
=> Proxy tag -> Proxy a -> [Text]
485+
toMultipartDescriptions _ proxyA = fmap (uncurry multipartSampleToDesc) samples
486+
where
487+
samples :: [(Text, MultipartData tag)]
488+
samples = toMultipartSamples proxyA
489+
490+
-- | Create a 'DocNote' that represents samples for this multipart input.
491+
toMultipartNotes
492+
:: ToMultipartSample tag a
493+
=> Int -> Proxy tag -> Proxy a -> DocNote
494+
toMultipartNotes maxSamples' proxyTag proxyA =
495+
let sampleLines = take maxSamples' $ toMultipartDescriptions proxyTag proxyA
496+
body =
497+
[ "This endpoint takes `multipart/form-data` requests. The following is " <>
498+
"a list of sample requests:"
499+
, foldMap (<> "\n") sampleLines
500+
]
501+
in DocNote "Multipart Request Samples" $ fmap unpack body
502+
503+
-- | Declare an instance of 'ToMultipartSample' for your 'MultipartForm' type
504+
-- to be able to use this 'HasDocs' instance.
505+
instance (HasDocs api, ToMultipartSample tag a) => HasDocs (MultipartForm tag a :> api) where
506+
docsFor
507+
:: Proxy (MultipartForm tag a :> api)
508+
-> (Endpoint, Action)
509+
-> DocOptions
510+
-> API
511+
docsFor _ (endpoint, action) opts =
512+
let newAction =
513+
action
514+
& notes <>~
515+
[ toMultipartNotes
516+
(view maxSamples opts)
517+
(Proxy :: Proxy tag)
518+
(Proxy :: Proxy a)
519+
]
520+
in docsFor (Proxy :: Proxy api) (endpoint, newAction) opts

stack.yaml

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# This file was automatically generated by 'stack init'
2+
#
3+
# Some commonly used options have been documented as comments in this file.
4+
# For advanced use and comprehensive documentation of the format, please see:
5+
# https://docs.haskellstack.org/en/stable/yaml_configuration/
6+
7+
# Resolver to choose a 'specific' stackage snapshot or a compiler version.
8+
# A snapshot resolver dictates the compiler version and the set of packages
9+
# to be used for project dependencies. For example:
10+
#
11+
# resolver: lts-3.5
12+
# resolver: nightly-2015-09-21
13+
# resolver: ghc-7.10.2
14+
# resolver: ghcjs-0.1.0_ghc-7.10.2
15+
# resolver:
16+
# name: custom-snapshot
17+
# location: "./custom-snapshot.yaml"
18+
resolver: lts-9.6
19+
20+
# User packages to be built.
21+
# Various formats can be used as shown in the example below.
22+
#
23+
# packages:
24+
# - some-directory
25+
# - https://example.com/foo/bar/baz-0.0.2.tar.gz
26+
# - location:
27+
# git: https://github.com/commercialhaskell/stack.git
28+
# commit: e7b331f14bcffb8367cd58fbfc8b40ec7642100a
29+
# - location: https://github.com/commercialhaskell/stack/commit/e7b331f14bcffb8367cd58fbfc8b40ec7642100a
30+
# extra-dep: true
31+
# subdirs:
32+
# - auto-update
33+
# - wai
34+
#
35+
# A package marked 'extra-dep: true' will only be built if demanded by a
36+
# non-dependency (i.e. a user package), and its test suites and benchmarks
37+
# will not be run. This is useful for tweaking upstream packages.
38+
packages:
39+
- .
40+
# Dependency packages to be pulled from upstream that are not in the resolver
41+
# (e.g., acme-missiles-0.3)
42+
extra-deps: []
43+
44+
# Override default flag values for local packages and extra-deps
45+
flags: {}
46+
47+
# Extra package databases containing global packages
48+
extra-package-dbs: []
49+
50+
# Control whether we use the GHC we find on the path
51+
# system-ghc: true
52+
#
53+
# Require a specific version of stack, using version ranges
54+
# require-stack-version: -any # Default
55+
# require-stack-version: ">=1.5"
56+
#
57+
# Override the architecture used by stack, especially useful on Windows
58+
# arch: i386
59+
# arch: x86_64
60+
#
61+
# Extra directories used by stack for building
62+
# extra-include-dirs: [/path/to/dir]
63+
# extra-lib-dirs: [/path/to/dir]
64+
#
65+
# Allow a newer minor version of GHC than the snapshot specifies
66+
# compiler-check: newer-minor

0 commit comments

Comments
 (0)