@@ -1,4 +1,5 @@
1{-# LANGUAGE TypeFamilies #-}
1{-# LANGUAGE TypeFamilies #-}
2+{-# LANGUAGE DataKinds #-}
2{-# LANGUAGE DeriveGeneric #-}
3{-# LANGUAGE DeriveGeneric #-}
3{-# LANGUAGE TupleSections #-}
4{-# LANGUAGE TupleSections #-}
4{-# LANGUAGE TypeOperators #-}
5{-# LANGUAGE TypeOperators #-}
@@ -91,6 +92,7 @@ module Servant.Docs
91 , sampleByteStrings
92 , sampleByteStrings
92 , ToParam(..)
93 , ToParam(..)
93 , ToCapture(..)
94 , ToCapture(..)
95 , -- * ADTs to represent an 'API'
97 , -- * ADTs to represent an 'API'
96 Method(..)
98 Method(..)
@@ -98,8 +100,10 @@ module Servant.Docs
98 , API, emptyAPI
100 , API, emptyAPI
99 , DocCapture(..), capSymbol, capDesc
101 , DocCapture(..), capSymbol, capDesc
100 , DocQueryParam(..), ParamKind(..), paramName, paramValues, paramDesc, paramKind
102 , DocQueryParam(..), ParamKind(..), paramName, paramValues, paramDesc, paramKind
103+ , DocNote(..), noteTitle, noteBody
104+ , DocIntro(..), Intro
101 , Response, respStatus, respBody, defResponse
105 , Response, respStatus, respBody, defResponse
102− , Action, captures, headers, params, rqbody, response, defAction
106+ , Action, captures, headers, notes, params, rqbody, response, defAction
105 , -- * Useful modules when defining your doc printers
109 , -- * Useful modules when defining your doc printers
@@ -110,6 +114,7 @@ module Servant.Docs
110import Control.Lens hiding (Action)
114import Control.Lens hiding (Action)
111import Data.Aeson
115import Data.Aeson
112import Data.Aeson.Encode.Pretty (encodePretty)
116import Data.Aeson.Encode.Pretty (encodePretty)
117+import Data.Ord(comparing)
113import Data.ByteString.Lazy.Char8 (ByteString)
118import Data.ByteString.Lazy.Char8 (ByteString)
114import Data.Hashable
119import Data.Hashable
115import Data.HashMap.Strict (HashMap)
120import Data.HashMap.Strict (HashMap)
@@ -188,12 +193,36 @@ defEndpoint = Endpoint [] DocGET
189instance Hashable Endpoint
194instance Hashable Endpoint
196+-- | Our API documentation type, a product of top-level information and a good
191−-- | Our API type, a good old hashmap from 'Endpoint' to 'Action'
197+-- old hashmap from 'Endpoint' to 'Action'
199+ { _apiIntros :: [DocIntro]
192−type API = HashMap Endpoint Action
200+ , _apiEndpoints :: HashMap Endpoint Action
201+ } deriving (Eq, Show)
203+instance Monoid API where
204+ API a1 b1 `mappend` API a2 b2 = API (a1 <> a2) (b1 <> b2)
205+ mempty = API mempty mempty
207+-- | A way for a developer to insert an introductory paragraph manually. This
208+-- is not to be used in server or client facing API types.
212+-- > type OurAPI = "users" :> Get [User]
213+-- > type IntroducedAPI = Intro "of human bondage" :> OurAPI
215+-- > instance ToIntro "of human bondage" where
216+-- > toIntro = DocIntro "A title for the intro section"
217+-- > [ "A blob of text that will be at the top."
218+-- > , "List elements are paragraphs."
221+data Intro (name :: Symbol)
194-- | An empty 'API'
223-- | An empty 'API'
195emptyAPI :: API
224emptyAPI :: API
196−emptyAPI = HM.empty
225+emptyAPI = mempty
198-- | A type to represent captures. Holds the name of the capture
227-- | A type to represent captures. Holds the name of the capture
199-- and a description.
228-- and a description.
@@ -216,6 +245,26 @@ data DocQueryParam = DocQueryParam
216 , _paramKind :: ParamKind
245 , _paramKind :: ParamKind
217 } deriving (Eq, Show)
246 } deriving (Eq, Show)
248+-- | An introductory paragraph for your documentation. You can attach these
249+-- with the 'Intro' type.
251+data DocIntro = DocIntro
252+ { _introTitle :: String -- ^ Appears above the intro blob
253+ , _introBody :: [String] -- ^ Each String is a paragraph.
254+ } deriving (Eq, Show)
256+instance Ord DocIntro where
257+ compare = comparing _introTitle
259+-- | A type to represent extra notes that may be attached to an 'Action'.
261+-- This is intended to be used when writing your own HasDocs instances to
262+-- add extra sections to your endpoint's documentation.
263+data DocNote = DocNote
264+ { _noteTitle :: String
265+ , _noteBody :: [String]
266+ } deriving (Eq, Show)
219-- | Type of GET parameter:
268-- | Type of GET parameter:
221-- - Normal corresponds to @QueryParam@, i.e your usual GET parameter
270-- - Normal corresponds to @QueryParam@, i.e your usual GET parameter
@@ -268,6 +317,7 @@ data Action = Action
268 { _captures :: [DocCapture] -- type collected + user supplied info
317 { _captures :: [DocCapture] -- type collected + user supplied info
269 , _headers :: [Text] -- type collected
318 , _headers :: [Text] -- type collected
270 , _params :: [DocQueryParam] -- type collected + user supplied info
319 , _params :: [DocQueryParam] -- type collected + user supplied info
320+ , _notes :: [DocNote] -- user supplied
271 , _rqbody :: Maybe ByteString -- user supplied
321 , _rqbody :: Maybe ByteString -- user supplied
272 , _response :: Response -- user supplied
322 , _response :: Response -- user supplied
273 } deriving (Eq, Show)
323 } deriving (Eq, Show)
@@ -284,6 +334,7 @@ data Action = Action
284defAction :: Action
334defAction :: Action
285defAction =
335defAction =
286 Action []
336 Action []
@@ -293,12 +344,15 @@ defAction =
293-- 'API' is a 'Monoid', so combine multiple endpoints with
344-- 'API' is a 'Monoid', so combine multiple endpoints with
294-- 'mappend' or '<>'.
345-- 'mappend' or '<>'.
295single :: Endpoint -> Action -> API
346single :: Endpoint -> Action -> API
296−single = HM.singleton
347+single e a = API mempty (HM.singleton e a)
298-- gimme some lenses
349-- gimme some lenses
299makeLenses ''Endpoint
351makeLenses ''Endpoint
300makeLenses ''DocCapture
352makeLenses ''DocCapture
301makeLenses ''DocQueryParam
353makeLenses ''DocQueryParam
354+makeLenses ''DocIntro
302makeLenses ''Response
356makeLenses ''Response
303makeLenses ''Action
357makeLenses ''Action
@@ -382,16 +436,26 @@ class ToParam t where
382class ToCapture c where
436class ToCapture c where
383 toCapture :: Proxy c -> DocCapture
437 toCapture :: Proxy c -> DocCapture
439+-- | The class to define the contents of an 'Intro'
440+-- Example of an instance:
442+-- > instance ToIntro "an intro" where
443+-- > toIntro _ = DocIntro "This is some text"
444+class ToIntro (intro :: Symbol) where
445+ toIntro :: Proxy intro -> DocIntro
385-- | Generate documentation in Markdown format for
447-- | Generate documentation in Markdown format for
386-- the given 'API'.
448-- the given 'API'.
387markdown :: API -> String
449markdown :: API -> String
450+markdown api = unlines $
451+ introsStr (api ^. apiIntros)
388−markdown = unlines . concat . map (uncurry printEndpoint) . HM.toList
452+ ++ (concatMap (uncurry printEndpoint) . HM.toList $ api ^. apiEndpoints)
390 where printEndpoint :: Endpoint -> Action -> [String]
454 where printEndpoint :: Endpoint -> Action -> [String]
391 printEndpoint endpoint action =
455 printEndpoint endpoint action =
458+ notesStr (action ^. notes) ++
395 capturesStr (action ^. captures) ++
459 capturesStr (action ^. captures) ++
396 headersStr (action ^. headers) ++
460 headersStr (action ^. headers) ++
397 paramsStr (action ^. params) ++
461 paramsStr (action ^. params) ++
@@ -399,13 +463,35 @@ markdown = unlines . concat . map (uncurry printEndpoint) . HM.toList
399 responseStr (action ^. response) ++
463 responseStr (action ^. response) ++
402− where str = show (endpoint^.method) ++ " " ++ showPath (endpoint^.path)
466+ where str = "## " ++ show (endpoint^.method)
403− len = length str
467+ ++ " " ++ showPath (endpoint^.path)
469+ introsStr :: [DocIntro] -> [String]
470+ introsStr = concatMap introStr
472+ introStr :: DocIntro -> [String]
474+ ("#### " ++ i ^. introTitle) :
476+ intersperse "" (i ^. introBody) ++
480+ notesStr :: [DocNote] -> [String]
481+ notesStr = concatMap noteStr
483+ noteStr :: DocNote -> [String]
485+ ("#### " ++ nt ^. noteTitle) :
487+ intersperse "" (nt ^. noteBody) ++
405 capturesStr :: [DocCapture] -> [String]
491 capturesStr :: [DocCapture] -> [String]
406 capturesStr [] = []
492 capturesStr [] = []
407 capturesStr l =
493 capturesStr l =
408− "**Captures**: " :
494+ "#### Captures:" :
410 map captureStr l ++
496 map captureStr l ++
@@ -423,7 +509,7 @@ markdown = unlines . concat . map (uncurry printEndpoint) . HM.toList
423 paramsStr :: [DocQueryParam] -> [String]
509 paramsStr :: [DocQueryParam] -> [String]
424 paramsStr [] = []
510 paramsStr [] = []
425 paramsStr l =
511 paramsStr l =
426− "**GET Parameters**: " :
512+ "#### GET Parameters:" :
428 map paramStr l ++
514 map paramStr l ++
@@ -448,7 +534,7 @@ markdown = unlines . concat . map (uncurry printEndpoint) . HM.toList
448 rqbodyStr :: Maybe ByteString -> [String]
534 rqbodyStr :: Maybe ByteString -> [String]
449 rqbodyStr Nothing = []
535 rqbodyStr Nothing = []
450 rqbodyStr (Just b) =
536 rqbodyStr (Just b) =
451− "**Request Body**: " :
537+ "#### Request Body:" :
452 jsonStr b
538 jsonStr b
454 jsonStr b =
540 jsonStr b =
@@ -461,7 +547,7 @@ markdown = unlines . concat . map (uncurry printEndpoint) . HM.toList
462 responseStr :: Response -> [String]
548 responseStr :: Response -> [String]
463 responseStr resp =
549 responseStr resp =
464− "**Response**: " :
550+ "#### Response:" :
466 (" - Status code " ++ show (resp ^. respStatus)) :
552 (" - Status code " ++ show (resp ^. respStatus)) :
@@ -607,6 +693,15 @@ instance (KnownSymbol path, HasDocs sublayout) => HasDocs (path :> sublayout) wh
607 endpoint' = endpoint & path <>~ [symbolVal pa]
693 endpoint' = endpoint & path <>~ [symbolVal pa]
608 pa = Proxy :: Proxy path
694 pa = Proxy :: Proxy path
696+instance (KnownSymbol intro, HasDocs sublayout, ToIntro intro)
697+ => HasDocs (Intro intro :> sublayout) where
700+ docsFor sublayoutP x & apiIntros %~ (toIntro intro <|)
701+ where sublayoutP = Proxy :: Proxy sublayout
702+ intro :: Proxy intro
612-- | Serve your API's docs as markdown embedded in an html \<pre> tag.
707-- | Serve your API's docs as markdown embedded in an html \<pre> tag.