Group mime-types together, and control how many get displayed
Closes #815
This commit is contained in:
parent
41d75b4de8
commit
09896b5f39
2 changed files with 64 additions and 15 deletions
|
@ -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
|
||||||
|
|
|
@ -22,22 +22,29 @@ 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
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue