Group mime-types together, and control how many get displayed

Closes #815
This commit is contained in:
Ivan Lazar Miljenovic 2017-10-05 16:26:39 +11:00
parent 41d75b4de8
commit 09896b5f39
2 changed files with 64 additions and 15 deletions

View File

@ -24,6 +24,9 @@
module Servant.Docs module Servant.Docs
( -- * 'HasDocs' class and key functions ( -- * 'HasDocs' class and key functions
HasDocs(..), docs, pretty, markdown HasDocs(..), docs, pretty, markdown
-- ** Customising generated documentation
, markdownWith, ApiOptions(..), defApiOptions
, requestExamples, responseExamples, ShowContentTypes(..)
-- * Generating docs with extra information -- * Generating docs with extra information
, docsWith, docsWithIntros, docsWithOptions , docsWith, docsWithIntros, docsWithOptions
, ExtraInfo(..), extraInfo , ExtraInfo(..), extraInfo

View File

@ -20,24 +20,31 @@
#include "overlapping-compat.h" #include "overlapping-compat.h"
module Servant.Docs.Internal where module Servant.Docs.Internal where
import Prelude () import Prelude ()
import Prelude.Compat import Prelude.Compat
import Control.Applicative import Control.Applicative
import Control.Arrow (second) import Control.Arrow (second)
import Control.Lens (makeLenses, mapped, over, traversed, view, (%~), import Control.Lens (makeLenses, mapped, over,
(&), (.~), (<>~), (^.), (|>)) traversed, view, (%~), (&), (.~),
(<>~), (^.), (|>))
import qualified Control.Monad.Omega as Omega import qualified Control.Monad.Omega as Omega
import Data.ByteString.Lazy.Char8 (ByteString)
import qualified Data.ByteString.Char8 as BSC import qualified Data.ByteString.Char8 as BSC
import Data.ByteString.Lazy.Char8 (ByteString)
import qualified Data.CaseInsensitive as CI import qualified Data.CaseInsensitive as CI
import Data.Foldable (fold)
import Data.Hashable (Hashable) import Data.Hashable (Hashable)
import Data.HashMap.Strict (HashMap) import Data.HashMap.Strict (HashMap)
import Data.List.Compat (intercalate, intersperse, sort) import Data.List.Compat (intercalate, intersperse, sort)
import Data.List.NonEmpty (NonEmpty ((:|)), groupWith)
import qualified Data.List.NonEmpty as NE
import Data.Maybe import Data.Maybe
import Data.Monoid (All (..), Any (..), Sum (..), Product (..), First (..), Last (..), Dual (..)) import Data.Monoid (All (..), Any (..), Dual (..),
import Data.Semigroup (Semigroup (..)) First (..), Last (..),
Product (..), Sum (..))
import Data.Ord (comparing) import Data.Ord (comparing)
import Data.Proxy (Proxy(Proxy)) import Data.Proxy (Proxy (Proxy))
import Data.Semigroup (Semigroup (..))
import Data.String.Conversions (cs) import Data.String.Conversions (cs)
import Data.Text (Text, unpack) import Data.Text (Text, unpack)
import GHC.Generics import GHC.Generics
@ -291,6 +298,23 @@ defAction =
single :: Endpoint -> Action -> API single :: Endpoint -> Action -> API
single e a = API mempty (HM.singleton e a) single e a = API mempty (HM.singleton e a)
-- | How many examples should be shown?
data ShowContentTypes = AllContentTypes | FirstContentType
deriving (Eq, Ord, Show, Read, Bounded, Enum)
-- | Customise how an 'API' is converted into documentation.
data ApiOptions = ApiOptions
{ _requestExamples :: !ShowContentTypes
, _responseExamples :: !ShowContentTypes
} deriving (Show)
-- | Default API generation options.
defApiOptions :: ApiOptions
defApiOptions = ApiOptions
{ _requestExamples = AllContentTypes
, _responseExamples = AllContentTypes
}
-- gimme some lenses -- gimme some lenses
makeLenses ''DocAuthentication makeLenses ''DocAuthentication
makeLenses ''DocOptions makeLenses ''DocOptions
@ -302,6 +326,7 @@ makeLenses ''DocIntro
makeLenses ''DocNote makeLenses ''DocNote
makeLenses ''Response makeLenses ''Response
makeLenses ''Action makeLenses ''Action
makeLenses ''ApiOptions
-- | Generate the docs for a given API that implements 'HasDocs'. This is the -- | Generate the docs for a given API that implements 'HasDocs'. This is the
-- default way to create documentation. -- default way to create documentation.
@ -518,7 +543,10 @@ class ToAuthInfo a where
-- | Generate documentation in Markdown format for -- | Generate documentation in Markdown format for
-- the given 'API'. -- the given 'API'.
markdown :: API -> String markdown :: API -> String
markdown api = unlines $ markdown = markdownWith defApiOptions
markdownWith :: ApiOptions -> API -> String
markdownWith ApiOptions{..} api = unlines $
introsStr (api ^. apiIntros) introsStr (api ^. apiIntros)
++ (concatMap (uncurry printEndpoint) . sort . HM.toList $ api ^. apiEndpoints) ++ (concatMap (uncurry printEndpoint) . sort . HM.toList $ api ^. apiEndpoints)
@ -632,19 +660,34 @@ markdown api = unlines $
rqbodyStr types s = rqbodyStr types s =
["#### Request:", ""] ["#### Request:", ""]
<> formatTypes types <> formatTypes types
<> formatBodies s <> formatBodies _requestExamples s
formatTypes [] = [] formatTypes [] = []
formatTypes ts = ["- Supported content types are:", ""] formatTypes ts = ["- Supported content types are:", ""]
<> map (\t -> " - `" <> show t <> "`") ts <> map (\t -> " - `" <> show t <> "`") ts
<> [""] <> [""]
formatBodies :: [(Text, M.MediaType, ByteString)] -> [String] -- This assumes that when the bodies are created, identical
formatBodies = concatMap formatBody -- labels and representations are located next to each other.
formatBodies :: ShowContentTypes -> [(Text, M.MediaType, ByteString)] -> [String]
formatBodies ex bds = concatMap formatBody (select bodyGroups)
where
bodyGroups :: [(Text, NonEmpty M.MediaType, ByteString)]
bodyGroups =
map (\grps -> let (t,_,b) = NE.head grps in (t, fmap (\(_,m,_) -> m) grps, b))
. groupWith (\(t,_,b) -> (t,b))
$ bds
formatBody (t, m, b) = select = case ex of
"- " <> cs t <> " (`" <> cs (show m) <> "`):" : AllContentTypes -> id
contentStr m b FirstContentType -> map (\(t,ms,b) -> (t, NE.head ms :| [], b))
formatBody :: (Text, NonEmpty M.MediaType, ByteString) -> [String]
formatBody (t, ms, b) =
"- " <> cs t <> " (" <> mediaList ms <> "):" :
contentStr (NE.head ms) b
where
mediaList = fold . NE.intersperse ", " . fmap (\m -> "`" ++ show m ++ "`")
markdownForType mime_type = markdownForType mime_type =
case (M.mainType mime_type, M.subType mime_type) of case (M.mainType mime_type, M.subType mime_type) of
@ -679,7 +722,10 @@ markdown api = unlines $
[] -> ["- No response body\n"] [] -> ["- No response body\n"]
[("", t, r)] -> "- Response body as below." : contentStr t r [("", t, r)] -> "- Response body as below." : contentStr t r
xs -> xs ->
formatBodies xs formatBodies _responseExamples xs
uncurry3 :: (a -> b -> c -> d) -> (a,b,c) -> d
uncurry3 f (a,b,c) = f a b c
-- * Instances -- * Instances