on master help sign in
Merge pull request #9 from anchor/wip-notes
2ea8a48Christian Marie
example/greet.hs+20−4
@@ -6,7 +6,7 @@
6{-# OPTIONS_GHC -fno-warn-orphans #-}
6{-# OPTIONS_GHC -fno-warn-orphans #-}
7import Data.Aeson
7import Data.Aeson
8import Data.Proxy
8import Data.Proxy
9−import Data.Text
9+import Data.Text(Text)
10import GHC.Generics
10import GHC.Generics
11import Servant.API
11import Servant.API
12import Servant.Docs
12import Servant.Docs
@@ -14,7 +14,7 @@ import Servant.Docs
14-- * Example
14-- * Example
15
15
16-- | A greet message data type
16-- | A greet message data type
17−newtype Greet = Greet { msg :: Text }
17+newtype Greet = Greet Text
18 deriving (Generic, Show)
18 deriving (Generic, Show)
19
19
20instance FromJSON Greet
20instance FromJSON Greet
@@ -33,7 +33,8 @@ instance ToParam (QueryParam "capital" Bool) where
33 toParam _ =
33 toParam _ =
34 DocQueryParam "capital"
34 DocQueryParam "capital"
35 ["true", "false"]
35 ["true", "false"]
36− "Get the greeting message in uppercase (true) or not (false). Default is false."
36+ "Get the greeting message in uppercase (true) or not (false).\
37+ \Default is false."
37 Normal
38 Normal
38
39
39instance ToParam (MatrixParam "lang" String) where
40instance ToParam (MatrixParam "lang" String) where
@@ -51,6 +52,17 @@ instance ToSample Greet where
51 , ("If you use ?capital=false", Greet "Hello, haskeller")
52 , ("If you use ?capital=false", Greet "Hello, haskeller")
52 ]
53 ]
53
54
55+intro1 :: DocIntro
56+intro1 = DocIntro "On proper introductions." -- The title
57+ [ "Hello there."
58+ , "As documentation is usually written for humans, it's often useful \
59+ \to introduce concepts with a few words." ] -- Elements are paragraphs
60+
61+intro2 :: DocIntro
62+intro2 = DocIntro "This title is below the last"
63+ [ "You'll also note that multiple intros are possible." ]
64+
65+
54-- API specification
66-- API specification
55type TestApi =
67type TestApi =
56 -- GET /hello/:name?capital={true, false} returns a Greet as JSON
68 -- GET /hello/:name?capital={true, false} returns a Greet as JSON
@@ -69,8 +81,12 @@ testApi = Proxy
69-- Generate the data that lets us have API docs. This
81-- Generate the data that lets us have API docs. This
70-- is derived from the type as well as from
82-- is derived from the type as well as from
71-- the 'ToCapture', 'ToParam' and 'ToSample' instances from above.
83-- the 'ToCapture', 'ToParam' and 'ToSample' instances from above.
84+--
85+-- If you didn't want intros you could just call:
86+--
87+-- > docs testAPI
72docsGreet :: API
88docsGreet :: API
73−docsGreet = docs testApi
89+docsGreet = docsWithIntros [intro1, intro2] testApi
74
90
75main :: IO ()
91main :: IO ()
76main = putStrLn $ markdown docsGreet
92main = putStrLn $ markdown docsGreet
example/greet.md+37−29
@@ -1,74 +1,82 @@
1−POST /greet
1+#### On proper introductions.
2−-----------
3
2
3+Hello there.
4+
5+As documentation is usually written for humans, it's often useful to introduce concepts with a few words.
6+
7+#### This title is below the last
8+
9+You'll also note that multiple intros are possible.
10+
11+## POST /greet
12+
4−**Request Body**:
13+#### Request Body:
5
14
6``` javascript
15``` javascript
7−{
8− "msg": "Hello, haskeller!"
16+"Hello, haskeller!"
9−}
10```
17```
11
18
12−**Response**:
19+#### Response:
13
20
14 - Status code 201
21 - Status code 201
15 - If you use ?capital=true
22 - If you use ?capital=true
16
23
17``` javascript
24``` javascript
18−{
19− "msg": "HELLO, HASKELLER"
25+"HELLO, HASKELLER"
20−}
21```
26```
22
27
23 - If you use ?capital=false
28 - If you use ?capital=false
24
29
25``` javascript
30``` javascript
26−{
27− "msg": "Hello, haskeller"
31+"Hello, haskeller"
28−}
29```
32```
30
33
31−GET /hello/:name
34+## GET /hello;lang=<value>/:name
32−----------------
33
35
34−**Captures**:
36+#### Captures:
35
37
36- *name*: name of the person to greet
38- *name*: name of the person to greet
37
39
40+#### Matrix Parameters**:
41+
42+**hello**:
43+
44+ - lang
45+ - **Values**: *en, sv, fr*
46+ - **Description**: Get the greeting message selected language. Default is en.
47+
48+
49+
50+
38−**GET Parameters**:
51+#### GET Parameters:
39
52
40 - capital
53 - capital
41 - **Values**: *true, false*
54 - **Values**: *true, false*
42− - **Description**: Get the greeting message in uppercase (true) or not (false). Default is false.
55+ - **Description**: Get the greeting message in uppercase (true) or not (false).Default is false.
43
56
44
57
45−**Response**:
58+#### Response:
46
59
47 - Status code 200
60 - Status code 200
48 - If you use ?capital=true
61 - If you use ?capital=true
49
62
50``` javascript
63``` javascript
51−{
52− "msg": "HELLO, HASKELLER"
64+"HELLO, HASKELLER"
53−}
54```
65```
55
66
56 - If you use ?capital=false
67 - If you use ?capital=false
57
68
58``` javascript
69``` javascript
59−{
60− "msg": "Hello, haskeller"
70+"Hello, haskeller"
61−}
62```
71```
63
72
64−DELETE /greet/:greetid
73+## DELETE /greet/:greetid
65−----------------------
66
74
67−**Captures**:
75+#### Captures:
68
76
69- *greetid*: identifier of the greet msg to remove
77- *greetid*: identifier of the greet msg to remove
70
78
71−**Response**:
79+#### Response:
72
80
73 - Status code 204
81 - Status code 204
74 - No response body
82 - No response body
src/Servant/Docs.hs+151−42
@@ -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 #-}
@@ -14,7 +15,12 @@
14--
15--
15-- @docs :: 'HasDocs' api => 'Proxy' api -> 'API'@
16-- @docs :: 'HasDocs' api => 'Proxy' api -> 'API'@
16--
17--
18+-- Alternately, if you wish to add one or more introductions to your
19+-- documentation, use 'docsWithIntros':
20+--
21+-- @docsWithIntros :: 'HasDocs' api => [DocIntro] -> 'Proxy' api -> 'API'@
22+--
17−-- You can then call 'markdown' on it:
23+-- You can then call 'markdown' on the 'API' value:
18--
24--
19-- @markdown :: 'API' -> String@
25-- @markdown :: 'API' -> String@
20--
26--
@@ -29,61 +35,100 @@
29-- markdown pretty printer in action:
35-- markdown pretty printer in action:
30--
36--
31-- > {-# LANGUAGE DataKinds #-}
37-- > {-# LANGUAGE DataKinds #-}
32−-- > {-# LANGUAGE PolyKinds #-}
33−-- > {-# LANGUAGE TypeFamilies #-}
34-- > {-# LANGUAGE DeriveGeneric #-}
38-- > {-# LANGUAGE DeriveGeneric #-}
35-- > {-# LANGUAGE TypeOperators #-}
39-- > {-# LANGUAGE TypeOperators #-}
36-- > {-# LANGUAGE FlexibleInstances #-}
40-- > {-# LANGUAGE FlexibleInstances #-}
37-- > {-# LANGUAGE OverloadedStrings #-}
41-- > {-# LANGUAGE OverloadedStrings #-}
38−-- >
42+-- > {-# OPTIONS_GHC -fno-warn-orphans #-}
43+-- > import Data.Aeson
39-- > import Data.Proxy
44-- > import Data.Proxy
40−-- > import Data.Text
45+-- > import Data.Text(Text)
46+-- > import GHC.Generics
41−-- > import Servant
47+-- > import Servant.API
48+-- > import Servant.Docs
42-- >
49-- >
43−-- > -- our type for a Greeting message
50+-- > -- * Example
51+-- >
52+-- > -- | A greet message data type
44−-- > data Greet = Greet { _msg :: Text }
53+-- > newtype Greet = Greet Text
45-- > deriving (Generic, Show)
54-- > deriving (Generic, Show)
46-- >
55-- >
47−-- > -- we get our JSON serialization for free
48-- > instance FromJSON Greet
56-- > instance FromJSON Greet
49-- > instance ToJSON Greet
57-- > instance ToJSON Greet
50-- >
58-- >
51−-- > -- we provide a sample value for the 'Greet' type
59+-- > -- We add some useful annotations to our captures,
60+-- > -- query parameters and request body to make the docs
61+-- > -- really helpful.
52−-- > instance ToSample Greet where
62+-- > instance ToCapture (Capture "name" Text) where
53−-- > toSample = Just g
63+-- > toCapture _ = DocCapture "name" "name of the person to greet"
54-- >
64-- >
55−-- > where g = Greet "Hello, haskeller!"
65+-- > instance ToCapture (Capture "greetid" Text) where
66+-- > toCapture _ = DocCapture "greetid" "identifier of the greet msg to remove"
56-- >
67-- >
57-- > instance ToParam (QueryParam "capital" Bool) where
68-- > instance ToParam (QueryParam "capital" Bool) where
58-- > toParam _ =
69-- > toParam _ =
59-- > DocQueryParam "capital"
70-- > DocQueryParam "capital"
60-- > ["true", "false"]
71-- > ["true", "false"]
61−-- > "Get the greeting message in uppercase (true) or not (false). Default is false."
72+-- > "Get the greeting message in uppercase (true) or not (false).\
73+-- > \Default is false."
74+-- > Normal
62-- >
75-- >
63−-- > instance ToCapture (Capture "name" Text) where
76+-- > instance ToParam (MatrixParam "lang" String) where
64−-- > toCapture _ = DocCapture "name" "name of the person to greet"
77+-- > toParam _ =
78+-- > DocQueryParam "lang"
79+-- > ["en", "sv", "fr"]
80+-- > "Get the greeting message selected language. Default is en."
81+-- > Normal
82+-- >
83+-- > instance ToSample Greet where
84+-- > toSample = Just $ Greet "Hello, haskeller!"
85+-- >
86+-- > toSamples =
87+-- > [ ("If you use ?capital=true", Greet "HELLO, HASKELLER")
88+-- > , ("If you use ?capital=false", Greet "Hello, haskeller")
89+-- > ]
90+-- >
91+-- > intro1 :: DocIntro
92+-- > intro1 = DocIntro "On proper introductions." -- The title
93+-- > [ "Hello there."
94+-- > , "As documentation is usually written for humans, it's often useful \
95+-- > \to introduce concepts with a few words." ] -- Elements are paragraphs
96+-- >
97+-- > intro2 :: DocIntro
98+-- > intro2 = DocIntro "This title is below the last"
99+-- > [ "You'll also note that multiple intros are possible." ]
65-- >
100-- >
66−-- > instance ToCapture (Capture "greetid" Text) where
67−-- > toCapture _ = DocCapture "greetid" "identifier of the greet msg to remove"
68-- >
101-- >
69-- > -- API specification
102-- > -- API specification
70-- > type TestApi =
103-- > type TestApi =
104+-- > -- GET /hello/:name?capital={true, false} returns a Greet as JSON
71−-- > "hello" :> Capture "name" Text :> QueryParam "capital" Bool :> Get Greet
105+-- > "hello" :> MatrixParam "lang" String :> Capture "name" Text :> QueryParam "capital" Bool :> Get Greet
106+-- >
107+-- > -- POST /greet with a Greet as JSON in the request body,
108+-- > -- returns a Greet as JSON
72−-- > :<|> "greet" :> RQBody Greet :> Post Greet
109+-- > :<|> "greet" :> ReqBody Greet :> Post Greet
110+-- >
111+-- > -- DELETE /greet/:greetid
73−-- > :<|> "delete" :> Capture "greetid" Text :> Delete
112+-- > :<|> "greet" :> Capture "greetid" Text :> Delete
74-- >
113-- >
75-- > testApi :: Proxy TestApi
114-- > testApi :: Proxy TestApi
76-- > testApi = Proxy
115-- > testApi = Proxy
77-- >
116-- >
78−-- > -- Generate the Documentation's ADT
117+-- > -- Generate the data that lets us have API docs. This
79−-- > greetDocs :: API
118+-- > -- is derived from the type as well as from
80−-- > greetDocs = docs testApi
119+-- > -- the 'ToCapture', 'ToParam' and 'ToSample' instances from above.
120+-- > --
121+-- > -- If you didn't want intros you could just call:
122+-- > --
123+-- > -- > docs testAPI
124+-- > docsGreet :: API
125+-- > docsGreet = docsWithIntros [intro1, intro2] testApi
81-- >
126-- >
82-- > main :: IO ()
127-- > main :: IO ()
83−-- > main = putStrLn $ markdown greetDocs
128+-- > main = putStrLn $ markdown docsGreet
84module Servant.Docs
129module Servant.Docs
85 ( -- * 'HasDocs' class and key functions
130 ( -- * 'HasDocs' class and key functions
86− HasDocs(..), docs, markdown
131+ HasDocs(..), docs, docsWithIntros, markdown
87
132
88 , -- * Classes you need to implement for your types
133 , -- * Classes you need to implement for your types
89 ToSample(..)
134 ToSample(..)
@@ -98,8 +143,10 @@ module Servant.Docs
98 , API, emptyAPI
143 , API, emptyAPI
99 , DocCapture(..), capSymbol, capDesc
144 , DocCapture(..), capSymbol, capDesc
100 , DocQueryParam(..), ParamKind(..), paramName, paramValues, paramDesc, paramKind
145 , DocQueryParam(..), ParamKind(..), paramName, paramValues, paramDesc, paramKind
146+ , DocNote(..), noteTitle, noteBody
147+ , DocIntro(..)
101 , Response, respStatus, respBody, defResponse
148 , Response, respStatus, respBody, defResponse
102− , Action, captures, headers, params, rqbody, response, defAction
149+ , Action, captures, headers, notes, params, rqbody, response, defAction
103 , single
150 , single
104
151
105 , -- * Useful modules when defining your doc printers
152 , -- * Useful modules when defining your doc printers
@@ -110,6 +157,7 @@ module Servant.Docs
110import Control.Lens hiding (Action)
157import Control.Lens hiding (Action)
111import Data.Aeson
158import Data.Aeson
112import Data.Aeson.Encode.Pretty (encodePretty)
159import Data.Aeson.Encode.Pretty (encodePretty)
160+import Data.Ord(comparing)
113import Data.ByteString.Lazy.Char8 (ByteString)
161import Data.ByteString.Lazy.Char8 (ByteString)
114import Data.Hashable
162import Data.Hashable
115import Data.HashMap.Strict (HashMap)
163import Data.HashMap.Strict (HashMap)
@@ -188,12 +236,20 @@ defEndpoint = Endpoint [] DocGET
188
236
189instance Hashable Endpoint
237instance Hashable Endpoint
190
238
239+-- | 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'
240+-- old hashmap from 'Endpoint' to 'Action'
241+data API = API
242+ { _apiIntros :: [DocIntro]
192−type API = HashMap Endpoint Action
243+ , _apiEndpoints :: HashMap Endpoint Action
244+ } deriving (Eq, Show)
245+
246+instance Monoid API where
247+ API a1 b1 `mappend` API a2 b2 = API (a1 <> a2) (b1 <> b2)
248+ mempty = API mempty mempty
193
249
194-- | An empty 'API'
250-- | An empty 'API'
195emptyAPI :: API
251emptyAPI :: API
196−emptyAPI = HM.empty
252+emptyAPI = mempty
197
253
198-- | A type to represent captures. Holds the name of the capture
254-- | A type to represent captures. Holds the name of the capture
199-- and a description.
255-- and a description.
@@ -216,6 +272,24 @@ data DocQueryParam = DocQueryParam
216 , _paramKind :: ParamKind
272 , _paramKind :: ParamKind
217 } deriving (Eq, Show)
273 } deriving (Eq, Show)
218
274
275+-- | An introductory paragraph for your documentation. You can pass these to
276+-- 'docsWithIntros'.
277+data DocIntro = DocIntro
278+ { _introTitle :: String -- ^ Appears above the intro blob
279+ , _introBody :: [String] -- ^ Each String is a paragraph.
280+ } deriving (Eq, Show)
281+
282+instance Ord DocIntro where
283+ compare = comparing _introTitle
284+
285+-- | A type to represent extra notes that may be attached to an 'Action'.
286+--
287+-- This is intended to be used when writing your own HasDocs instances to
288+-- add extra sections to your endpoint's documentation.
289+data DocNote = DocNote
290+ { _noteTitle :: String
291+ , _noteBody :: [String]
292+ } deriving (Eq, Show)
219
293
220-- | Type of GET parameter:
294-- | Type of GET parameter:
221--
295--
@@ -269,6 +343,7 @@ data Action = Action
269 { _captures :: [DocCapture] -- type collected + user supplied info
343 { _captures :: [DocCapture] -- type collected + user supplied info
270 , _headers :: [Text] -- type collected
344 , _headers :: [Text] -- type collected
271 , _params :: [DocQueryParam] -- type collected + user supplied info
345 , _params :: [DocQueryParam] -- type collected + user supplied info
346+ , _notes :: [DocNote] -- user supplied
272 , _mxParams :: [(String, [DocQueryParam])] -- type collected + user supplied info
347 , _mxParams :: [(String, [DocQueryParam])] -- type collected + user supplied info
273 , _rqbody :: Maybe ByteString -- user supplied
348 , _rqbody :: Maybe ByteString -- user supplied
274 , _response :: Response -- user supplied
349 , _response :: Response -- user supplied
@@ -289,6 +364,7 @@ defAction =
289 []
364 []
290 []
365 []
291 []
366 []
367+ []
292 Nothing
368 Nothing
293 defResponse
369 defResponse
294
370
@@ -296,19 +372,28 @@ defAction =
296-- 'API' is a 'Monoid', so combine multiple endpoints with
372-- 'API' is a 'Monoid', so combine multiple endpoints with
297-- 'mappend' or '<>'.
373-- 'mappend' or '<>'.
298single :: Endpoint -> Action -> API
374single :: Endpoint -> Action -> API
299−single = HM.singleton
375+single e a = API mempty (HM.singleton e a)
300
376
301-- gimme some lenses
377-- gimme some lenses
378+makeLenses ''API
302makeLenses ''Endpoint
379makeLenses ''Endpoint
303makeLenses ''DocCapture
380makeLenses ''DocCapture
304makeLenses ''DocQueryParam
381makeLenses ''DocQueryParam
382+makeLenses ''DocIntro
383+makeLenses ''DocNote
305makeLenses ''Response
384makeLenses ''Response
306makeLenses ''Action
385makeLenses ''Action
307
386
308−-- | Generate the docs for a given API that implements 'HasDocs'.
387+-- | Generate the docs for a given API that implements 'HasDocs'. This is the
388+-- default way to create documentation.
309docs :: HasDocs layout => Proxy layout -> API
389docs :: HasDocs layout => Proxy layout -> API
310docs p = docsFor p (defEndpoint, defAction)
390docs p = docsFor p (defEndpoint, defAction)
311
391
392+-- | Generate the docs for a given API that implements 'HasDocs' with with any
393+-- number of introduction(s)
394+docsWithIntros :: HasDocs layout => [DocIntro] -> Proxy layout -> API
395+docsWithIntros intros p = docs p & apiIntros <>~ intros
396+
312-- | The class that abstracts away the impact of API combinators
397-- | The class that abstracts away the impact of API combinators
313-- on documentation generation.
398-- on documentation generation.
314class HasDocs layout where
399class HasDocs layout where
@@ -388,13 +473,15 @@ class ToCapture c where
388-- | Generate documentation in Markdown format for
473-- | Generate documentation in Markdown format for
389-- the given 'API'.
474-- the given 'API'.
390markdown :: API -> String
475markdown :: API -> String
476+markdown api = unlines $
477+ introsStr (api ^. apiIntros)
391−markdown = unlines . concat . map (uncurry printEndpoint) . HM.toList
478+ ++ (concatMap (uncurry printEndpoint) . HM.toList $ api ^. apiEndpoints)
392
479
393 where printEndpoint :: Endpoint -> Action -> [String]
480 where printEndpoint :: Endpoint -> Action -> [String]
394 printEndpoint endpoint action =
481 printEndpoint endpoint action =
395 str :
482 str :
396− replicate len '-' :
397 "" :
483 "" :
484+ notesStr (action ^. notes) ++
398 capturesStr (action ^. captures) ++
485 capturesStr (action ^. captures) ++
399 mxParamsStr (action ^. mxParams) ++
486 mxParamsStr (action ^. mxParams) ++
400 headersStr (action ^. headers) ++
487 headersStr (action ^. headers) ++
@@ -403,13 +490,35 @@ markdown = unlines . concat . map (uncurry printEndpoint) . HM.toList
403 responseStr (action ^. response) ++
490 responseStr (action ^. response) ++
404 []
491 []
405
492
406− where str = show (endpoint^.method) ++ " " ++ showPath (endpoint^.path)
493+ where str = "## " ++ show (endpoint^.method)
407− len = length str
494+ ++ " " ++ showPath (endpoint^.path)
495+
496+ introsStr :: [DocIntro] -> [String]
497+ introsStr = concatMap introStr
498+
499+ introStr :: DocIntro -> [String]
500+ introStr i =
501+ ("#### " ++ i ^. introTitle) :
502+ "" :
503+ intersperse "" (i ^. introBody) ++
504+ "" :
505+ []
506+
507+ notesStr :: [DocNote] -> [String]
508+ notesStr = concatMap noteStr
509+
510+ noteStr :: DocNote -> [String]
511+ noteStr nt =
512+ ("#### " ++ nt ^. noteTitle) :
513+ "" :
514+ intersperse "" (nt ^. noteBody) ++
515+ "" :
516+ []
408
517
409 capturesStr :: [DocCapture] -> [String]
518 capturesStr :: [DocCapture] -> [String]
410 capturesStr [] = []
519 capturesStr [] = []
411 capturesStr l =
520 capturesStr l =
412− "**Captures**: " :
521+ "#### Captures:" :
413 "" :
522 "" :
414 map captureStr l ++
523 map captureStr l ++
415 "" :
524 "" :
@@ -420,14 +529,14 @@ markdown = unlines . concat . map (uncurry printEndpoint) . HM.toList
420 mxParamsStr :: [(String, [DocQueryParam])] -> [String]
529 mxParamsStr :: [(String, [DocQueryParam])] -> [String]
421 mxParamsStr [] = []
530 mxParamsStr [] = []
422 mxParamsStr l =
531 mxParamsStr l =
423− "**Matrix Parameters**: " :
532+ "#### Matrix Parameters:" :
424 "" :
533 "" :
425 map segmentStr l ++
534 map segmentStr l ++
426 "" :
535 "" :
427 []
536 []
428 segmentStr :: (String, [DocQueryParam]) -> String
537 segmentStr :: (String, [DocQueryParam]) -> String
429 segmentStr (segment, l) = unlines $
538 segmentStr (segment, l) = unlines $
430− ("**" ++ segment ++ "**: ") :
539+ ("**" ++ segment ++ "**:") :
431 "" :
540 "" :
432 map paramStr l ++
541 map paramStr l ++
433 "" :
542 "" :
@@ -443,7 +552,7 @@ markdown = unlines . concat . map (uncurry printEndpoint) . HM.toList
443 paramsStr :: [DocQueryParam] -> [String]
552 paramsStr :: [DocQueryParam] -> [String]
444 paramsStr [] = []
553 paramsStr [] = []
445 paramsStr l =
554 paramsStr l =
446− "**GET Parameters**: " :
555+ "#### GET Parameters:" :
447 "" :
556 "" :
448 map paramStr l ++
557 map paramStr l ++
449 "" :
558 "" :
@@ -468,7 +577,7 @@ markdown = unlines . concat . map (uncurry printEndpoint) . HM.toList
468 rqbodyStr :: Maybe ByteString -> [String]
577 rqbodyStr :: Maybe ByteString -> [String]
469 rqbodyStr Nothing = []
578 rqbodyStr Nothing = []
470 rqbodyStr (Just b) =
579 rqbodyStr (Just b) =
471− "**Request Body**: " :
580+ "#### Request Body:" :
472 jsonStr b
581 jsonStr b
473
582
474 jsonStr b =
583 jsonStr b =
@@ -481,7 +590,7 @@ markdown = unlines . concat . map (uncurry printEndpoint) . HM.toList
481
590
482 responseStr :: Response -> [String]
591 responseStr :: Response -> [String]
483 responseStr resp =
592 responseStr resp =
484− "**Response**: " :
593+ "#### Response:" :
485 "" :
594 "" :
486 (" - Status code " ++ show (resp ^. respStatus)) :
595 (" - Status code " ++ show (resp ^. respStatus)) :
487 bodies
596 bodies