@@ -25,8 +25,8 @@ import Control.Applicative
2525import Control.Arrow
2626 (second)
2727import Control.Lens
28− (makeLenses, mapped, over, traversed, view, (%~), (&), (.~),
29− (<>~), (^.), (|>))
28+ (makeLenses, mapped, over, set, traversed, view, (%~), (&),
29+ (.~), (<>~), (^.), (|>))
3030import qualified Data.ByteString.Char8 as BSC
3131import Data.ByteString.Lazy.Char8
3232 (ByteString)
@@ -64,7 +64,7 @@ import Servant.API
6464import Servant.API.ContentTypes
6565import Servant.API.TypeLevel
6666
67−import qualified Data.Universe.Helpers as U
67+import qualified Data.Universe.Helpers as U
6868
6969import qualified Data.HashMap.Strict as HM
7070import qualified Data.Text as T
@@ -161,6 +161,20 @@ data DocQueryParam = DocQueryParam
161161 , _paramKind :: ParamKind
162162 } deriving (Eq, Ord, Show)
163163
164+-- | A type to represent fragment. Holds the name of the fragment and its description.
165+--
166+-- Write a 'ToFragment' instance for your fragment types.
167+data DocFragment = DocFragment
168+ { _fragSymbol :: String -- type supplied
169+ , _fragDesc :: String -- user supplied
170+ } deriving (Eq, Ord, Show)
171+
172+-- | There should be at most one 'Fragment' per API endpoint.
173+-- So here we are keeping the first occurrence.
174+combineFragment :: Maybe DocFragment -> Maybe DocFragment -> Maybe DocFragment
175+Nothing `combineFragment` mdocFragment = mdocFragment
176+Just docFragment `combineFragment` _ = Just docFragment
177+
164178-- | An introductory paragraph for your documentation. You can pass these to
165179-- 'docsWithIntros'.
166180data DocIntro = DocIntro
@@ -283,6 +297,7 @@ data Action = Action
283297 , _captures :: [DocCapture] -- type collected + user supplied info
284298 , _headers :: [Text] -- type collected
285299 , _params :: [DocQueryParam] -- type collected + user supplied info
300+ , _fragment :: Maybe DocFragment -- type collected + user supplied info
286301 , _notes :: [DocNote] -- user supplied
287302 , _mxParams :: [(String, [DocQueryParam])] -- type collected + user supplied info
288303 , _rqtypes :: [M.MediaType] -- type collected
@@ -296,8 +311,9 @@ data Action = Action
296311-- As such, we invent a non-commutative, left associative operation
297312-- 'combineAction' to mush two together taking the response from the very left.
298313combineAction :: Action -> Action -> Action
299−Action a c h p n m ts body resp `combineAction` Action a' c' h' p' n' m' ts' body' resp' =
300− Action (a <> a') (c <> c') (h <> h') (p <> p') (n <> n') (m <> m') (ts <> ts') (body <> body') (resp `combineResponse` resp')
314+Action a c h p f n m ts body resp
315+ `combineAction` Action a' c' h' p' f' n' m' ts' body' resp' =
316+ Action (a <> a') (c <> c') (h <> h') (p <> p') (f `combineFragment` f') (n <> n') (m <> m') (ts <> ts') (body <> body') (resp `combineResponse` resp')
301317
302318-- | Default 'Action'. Has no 'captures', no query 'params', expects
303319-- no request body ('rqbody') and the typical response is 'defResponse'.
@@ -305,10 +321,10 @@ Action a c h p n m ts body resp `combineAction` Action a' c' h' p' n' m' ts' bod
305321-- Tweakable with lenses.
306322--
307323-- >>> defAction
308−-- Action {_authInfo = [], _captures = [], _headers = [], _params = [], _notes = [], _mxParams = [], _rqtypes = [], _rqbody = [], _response = Response {_respStatus = 200, _respTypes = [], _respBody = [], _respHeaders = []}}
324+-- Action {_authInfo = [], _captures = [], _headers = [], _params = [], _fragment = Nothing, _notes = [], _mxParams = [], _rqtypes = [], _rqbody = [], _response = Response {_respStatus = 200, _respTypes = [], _respBody = [], _respHeaders = []}}
309325--
310326-- >>> defAction & response.respStatus .~ 201
311−-- Action {_authInfo = [], _captures = [], _headers = [], _params = [], _notes = [], _mxParams = [], _rqtypes = [], _rqbody = [], _response = Response {_respStatus = 201, _respTypes = [], _respBody = [], _respHeaders = []}}
327+-- Action {_authInfo = [], _captures = [], _headers = [], _params = [], _fragment = Nothing, _notes = [], _mxParams = [], _rqtypes = [], _rqbody = [], _response = Response {_respStatus = 201, _respTypes = [], _respBody = [], _respHeaders = []}}
312328--
313329defAction :: Action
314330defAction =
@@ -316,6 +332,7 @@ defAction =
316332 []
317333 []
318334 []
335+ Nothing
319336 []
320337 []
321338 []
@@ -368,6 +385,7 @@ makeLenses ''API
368385makeLenses ''Endpoint
369386makeLenses ''DocCapture
370387makeLenses ''DocQueryParam
388+makeLenses ''DocFragment
371389makeLenses ''DocIntro
372390makeLenses ''DocNote
373391makeLenses ''Response
@@ -587,6 +605,15 @@ class ToCapture c where
587605class ToAuthInfo a where
588606 toAuthInfo :: Proxy a -> DocAuthentication
589607
608+-- | The class that helps us get documentation for URL fragments.
609+--
610+-- Example of an instance:
611+--
612+-- > instance ToFragment (Fragment a) where
613+-- > toFragment _ = DocFragment "fragment" "fragment description"
614+class ToFragment t where
615+ toFragment :: Proxy t -> DocFragment
616+
590617-- | Generate documentation in Markdown format for
591618-- the given 'API'.
592619--
@@ -629,6 +656,7 @@ markdownWith RenderingOptions{..} api = unlines $
629656 capturesStr (action ^. captures) ++
630657 headersStr (action ^. headers) ++
631658 paramsStr meth (action ^. params) ++
659+ fragmentStr (action ^. fragment) ++
632660 rqbodyStr (action ^. rqtypes) (action ^. rqbody) ++
633661 responseStr (action ^. response) ++
634662 []
@@ -730,6 +758,14 @@ markdownWith RenderingOptions{..} api = unlines $
730758
731759 where values = param ^. paramValues
732760
761+ fragmentStr :: Maybe DocFragment -> [String]
762+ fragmentStr Nothing = []
763+ fragmentStr (Just frag) =
764+ [ "### Fragment:", ""
765+ , "- *" ++ (frag ^. fragSymbol) ++ "*: " ++ (frag ^. fragDesc)
766+ , ""
767+ ]
768+
733769 rqbodyStr :: [M.MediaType] -> [(Text, M.MediaType, ByteString)]-> [String]
734770 rqbodyStr [] [] = []
735771 rqbodyStr types s =
@@ -959,6 +995,15 @@ instance (KnownSymbol sym, ToParam (QueryFlag sym), HasDocs api)
959995 paramP = Proxy :: Proxy (QueryFlag sym)
960996 action' = over params (|> toParam paramP) action
961997
998+instance (ToFragment (Fragment a), HasDocs api)
999+ => HasDocs (Fragment a :> api) where
1000+
1001+ docsFor Proxy (endpoint, action) =
1002+ docsFor subApiP (endpoint, action')
1003+
1004+ where subApiP = Proxy :: Proxy api
1005+ fragmentP = Proxy :: Proxy (Fragment a)
1006+ action' = set fragment (Just (toFragment fragmentP)) action
9621007
9631008instance HasDocs Raw where
9641009 docsFor _proxy (endpoint, action) _ =