From b935f2818513e82725f3402dc14967e118d4a8d9 Mon Sep 17 00:00:00 2001 From: Christian Marie Date: Wed, 18 Feb 2015 12:49:08 +1100 Subject: [PATCH] Improve documentation. I also slipped a change into the Matrix Paramters section markdown generation, there is one less (superfluous) newline there now. --- example/greet.hs | 18 ++++++++---- example/greet.md | 16 ++++++++-- src/Servant/Docs.hs | 72 +++++++++++++++++++++++++++++++++------------ 3 files changed, 80 insertions(+), 26 deletions(-) diff --git a/example/greet.hs b/example/greet.hs index b24319f7..e5d448d1 100644 --- a/example/greet.hs +++ b/example/greet.hs @@ -52,6 +52,11 @@ instance ToSample Greet where , ("If you use ?capital=false", Greet "Hello, haskeller") ] +-- We define some introductory sections, these will appear at the top of the +-- documentation. +-- +-- We pass them in with 'docsWith', below. If you only want to add +-- introductions, you may use 'docsWithIntros' intro1 :: DocIntro intro1 = DocIntro "On proper introductions." -- The title [ "Hello there." @@ -78,8 +83,11 @@ type TestApi = testApi :: Proxy TestApi testApi = Proxy -extras :: ExtraInfo TestApi -extras = +-- Build some extra information for the DELETE /greet/:greetid endpoint. We +-- want to add documentation about a secret unicorn header and some extra +-- notes. +extra :: ExtraInfo TestApi +extra = safeInfo (Proxy :: Proxy ("greet" :> Capture "greetid" Text :> Delete)) $ defAction & headers <>~ ["unicorns"] & notes <>~ [ DocNote "Title" ["This is some text"] @@ -90,11 +98,11 @@ extras = -- is derived from the type as well as from -- the 'ToCapture', 'ToParam' and 'ToSample' instances from above. -- --- If you didn't want intros you could just call: +-- If you didn't want intros and extra information, you could just call: -- --- > docs testAPI +-- > docs testAPI :: API docsGreet :: API -docsGreet = docsWith [intro1, intro2] extras testApi +docsGreet = docsWith [intro1, intro2] extra testApi main :: IO () main = putStrLn $ markdown docsGreet diff --git a/example/greet.md b/example/greet.md index 948b89ed..16275e39 100644 --- a/example/greet.md +++ b/example/greet.md @@ -37,7 +37,7 @@ You'll also note that multiple intros are possible. - *name*: name of the person to greet -#### Matrix Parameters**: +#### Matrix Parameters: **hello**: @@ -47,7 +47,6 @@ You'll also note that multiple intros are possible. - #### GET Parameters: - capital @@ -72,13 +71,24 @@ You'll also note that multiple intros are possible. ## DELETE /greet/:greetid +#### Title + +This is some text + +#### Second secton + +And some more + #### Captures: - *greetid*: identifier of the greet msg to remove + +- This endpoint is sensitive to the value of the **unicorns** HTTP header. + #### Response: - - Status code 204 + - Status code 200 - No response body diff --git a/src/Servant/Docs.hs b/src/Servant/Docs.hs index a512919d..2cd4a564 100644 --- a/src/Servant/Docs.hs +++ b/src/Servant/Docs.hs @@ -91,6 +91,11 @@ -- > , ("If you use ?capital=false", Greet "Hello, haskeller") -- > ] -- > +-- > -- We define some introductory sections, these will appear at the top of the +-- > -- documentation. +-- > -- +-- > -- We pass them in with 'docsWith', below. If you only want to add +-- > -- introductions, you may use 'docsWithIntros' -- > intro1 :: DocIntro -- > intro1 = DocIntro "On proper introductions." -- The title -- > [ "Hello there." @@ -117,15 +122,26 @@ -- > testApi :: Proxy TestApi -- > testApi = Proxy -- > +-- > -- Build some extra information for the DELETE /greet/:greetid endpoint. We +-- > -- want to add documentation about a secret unicorn header and some extra +-- > -- notes. +-- > extra :: ExtraInfo TestApi +-- > extra = +-- > safeInfo (Proxy :: Proxy ("greet" :> Capture "greetid" Text :> Delete)) $ +-- > defAction & headers <>~ ["unicorns"] +-- > & notes <>~ [ DocNote "Title" ["This is some text"] +-- > , DocNote "Second secton" ["And some more"] +-- > ] +-- > -- > -- Generate the data that lets us have API docs. This -- > -- is derived from the type as well as from -- > -- the 'ToCapture', 'ToParam' and 'ToSample' instances from above. -- > -- --- > -- If you didn't want intros you could just call: +-- > -- If you didn't want intros and extra information, you could just call: -- > -- --- > -- > docs testAPI +-- > -- > docs testAPI :: API -- > docsGreet :: API --- > docsGreet = docsWithIntros [intro1, intro2] testApi +-- > docsGreet = docsWith [intro1, intro2] extra testApi -- > -- > main :: IO () -- > main = putStrLn $ markdown docsGreet @@ -298,6 +314,17 @@ data DocNote = DocNote , _noteBody :: [String] } deriving (Eq, Show) +-- | Type of extra information that a user may wish to "union" with their +-- documentation. +-- +-- These are intended to be built using safeInfo. +-- Multiple ExtraInfo may be combined with the monoid instance. +newtype ExtraInfo layout = ExtraInfo (HashMap Endpoint Action) +instance Monoid (ExtraInfo a) where + mempty = ExtraInfo mempty + ExtraInfo a `mappend` ExtraInfo b = + ExtraInfo $ HM.unionWith combineAction a b + -- | Type of GET parameter: -- -- - Normal corresponds to @QueryParam@, i.e your usual GET parameter @@ -405,13 +432,6 @@ makeLenses ''Action docs :: HasDocs layout => Proxy layout -> API docs p = docsFor p (defEndpoint, defAction) - -newtype ExtraInfo layout = ExtraInfo (HashMap Endpoint Action) -instance Monoid (ExtraInfo a) where - mempty = ExtraInfo mempty - ExtraInfo a `mappend` ExtraInfo b = - ExtraInfo $ HM.unionWith combineAction a b - -- | Closed type family, check if endpoint is exactly within API. -- We aren't sure what affects how an Endpoint is built up, so we require an @@ -421,6 +441,18 @@ type family IsIn (endpoint :: *) (api :: *) :: Constraint where IsIn (e :> sa) (e :> sb) = IsIn sa sb IsIn e e = () +-- | Create an 'ExtraInfo' that is garunteed to be within the given API layout. +-- +-- The safety here is to ensure that you only add custom documentation to an +-- endpoint that actually exists within your API. +-- +-- > extra :: ExtraInfo TestApi +-- > extra = +-- > safeInfo (Proxy :: Proxy ("greet" :> Capture "greetid" Text :> Delete)) $ +-- > defAction & headers <>~ ["unicorns"] +-- > & notes <>~ [ DocNote "Title" ["This is some text"] +-- > , DocNote "Second secton" ["And some more"] +-- > ] safeInfo :: (IsIn endpoint layout, HasLink endpoint, HasDocs endpoint) => Proxy endpoint -> Action -> ExtraInfo layout @@ -430,12 +462,18 @@ safeInfo p action = -- point at one endpoint. in ExtraInfo $ api ^. apiEndpoints & traversed .~ action --- | Generate documentation given some initial state, in which you may wish to --- note that certain endpoints are special in some way. +-- | Generate documentation given some extra introductions (in the form of +-- 'DocInfo') and some extra endpoint documentation (in the form of +-- 'ExtraInfo'. +-- +-- The extra introductions will be prepended to the top of the documentation, +-- before the specific endpoint documentation. The extra endpoint documentation +-- will be "unioned" with the automatically generated endpoint documentation. +-- +-- You are expected to build up the ExtraInfo with the Monoid instance and +-- 'safeInfo'. -- -- If you only want to add an introduction, use 'docsWithIntros'. --- --- You are expected to build up the SafeMap with safeEntry docsWith :: HasDocs layout => [DocIntro] -> ExtraInfo layout @@ -485,7 +523,7 @@ class HasDocs layout where -- You can also instantiate this class using 'toSamples' instead of -- 'toSample': it lets you specify different responses along with -- some context (as 'Text') that explains when you're supposed to --- get the corresponding response. +-- get the corresponding response. class ToJSON a => ToSample a where {-# MINIMAL (toSample | toSamples) #-} toSample :: Maybe a @@ -588,9 +626,7 @@ markdown api = unlines $ mxParamsStr l = "#### Matrix Parameters:" : "" : - map segmentStr l ++ - "" : - [] + map segmentStr l segmentStr :: (String, [DocQueryParam]) -> String segmentStr (segment, l) = unlines $ ("**" ++ segment ++ "**:") :