From 061bf7a5b113396a6533260770aec4784da1d5bd Mon Sep 17 00:00:00 2001 From: Ivan Lazar Miljenovic Date: Thu, 5 Oct 2017 14:32:06 +1100 Subject: [PATCH 01/14] Add markdown type for text/xml As it's an alternative mime-type for XML --- servant-docs/src/Servant/Docs/Internal.hs | 1 + 1 file changed, 1 insertion(+) diff --git a/servant-docs/src/Servant/Docs/Internal.hs b/servant-docs/src/Servant/Docs/Internal.hs index fdf1b855..7bf3481f 100644 --- a/servant-docs/src/Servant/Docs/Internal.hs +++ b/servant-docs/src/Servant/Docs/Internal.hs @@ -647,6 +647,7 @@ markdown api = unlines $ case (M.mainType mime_type, M.subType mime_type) of ("text", "html") -> "html" ("application", "xml") -> "xml" + ("text", "xml") -> "xml" ("application", "json") -> "javascript" ("application", "javascript") -> "javascript" ("text", "css") -> "css" From 4f8df0ebe205a90139c4901ac7a7cee613564ebf Mon Sep 17 00:00:00 2001 From: Ivan Lazar Miljenovic Date: Thu, 5 Oct 2017 14:41:40 +1100 Subject: [PATCH 02/14] Make sure code blocks are indented in markdown documentation This relies on the behaviour of pandoc, and as such may not apply to other Markdown renderers. Before this change, you would have something like: > - Example: `application/json` > > ```javascript > "HELLO, HASKELLER" > ``` When converting this to HTML, PDF, etc. the code block is _not_ contained within the bullet point. With this change, the generated markdown looks like: > - Example: `application/json` > > ```javascript > "HELLO, HASKELLER" > ``` With pandoc at least, this effectively indents the entire code block to be under the bullet point, which is the intended effect. Note that the code itself is _not_ indented (which might break other Markdown renderers) as to do so would require splitting on newlines, which may have unintended consequences when dealing with generated values (may contain `\r\n`, etc.). --- servant-docs/src/Servant/Docs/Internal.hs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/servant-docs/src/Servant/Docs/Internal.hs b/servant-docs/src/Servant/Docs/Internal.hs index 7bf3481f..bd43bba8 100644 --- a/servant-docs/src/Servant/Docs/Internal.hs +++ b/servant-docs/src/Servant/Docs/Internal.hs @@ -653,11 +653,12 @@ markdown api = unlines $ ("text", "css") -> "css" (_, _) -> "" + contentStr mime_type body = "" : - "```" <> markdownForType mime_type : + " ```" <> markdownForType mime_type : cs body : - "```" : + " ```" : "" : [] From 41d75b4de80b99da3896ad3717b60e94ad70036e Mon Sep 17 00:00:00 2001 From: Ivan Lazar Miljenovic Date: Thu, 5 Oct 2017 14:48:20 +1100 Subject: [PATCH 03/14] Combine rendering of code samples for request/response bodies This does mean that the "Example" portion for reqeust bodies is lost though. --- servant-docs/src/Servant/Docs/Internal.hs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/servant-docs/src/Servant/Docs/Internal.hs b/servant-docs/src/Servant/Docs/Internal.hs index bd43bba8..8c064d67 100644 --- a/servant-docs/src/Servant/Docs/Internal.hs +++ b/servant-docs/src/Servant/Docs/Internal.hs @@ -632,15 +632,18 @@ markdown api = unlines $ rqbodyStr types s = ["#### Request:", ""] <> formatTypes types - <> concatMap formatBody s + <> formatBodies s formatTypes [] = [] formatTypes ts = ["- Supported content types are:", ""] <> map (\t -> " - `" <> show t <> "`") ts <> [""] + formatBodies :: [(Text, M.MediaType, ByteString)] -> [String] + formatBodies = concatMap formatBody + formatBody (t, m, b) = - "- Example (" <> cs t <> "): `" <> cs (show m) <> "`" : + "- " <> cs t <> " (`" <> cs (show m) <> "`):" : contentStr m b markdownForType mime_type = @@ -676,7 +679,7 @@ markdown api = unlines $ [] -> ["- No response body\n"] [("", t, r)] -> "- Response body as below." : contentStr t r xs -> - concatMap (\(ctx, t, r) -> ("- " <> T.unpack ctx <> " (`" <> cs (show t) <> "`)") : contentStr t r) xs + formatBodies xs -- * Instances From 09896b5f3925c5dcc123f58d7edea80f873d8f05 Mon Sep 17 00:00:00 2001 From: Ivan Lazar Miljenovic Date: Thu, 5 Oct 2017 16:26:39 +1100 Subject: [PATCH 04/14] Group mime-types together, and control how many get displayed Closes #815 --- servant-docs/src/Servant/Docs.hs | 3 + servant-docs/src/Servant/Docs/Internal.hs | 76 ++++++++++++++++++----- 2 files changed, 64 insertions(+), 15 deletions(-) diff --git a/servant-docs/src/Servant/Docs.hs b/servant-docs/src/Servant/Docs.hs index 7209e258..5bf759e3 100644 --- a/servant-docs/src/Servant/Docs.hs +++ b/servant-docs/src/Servant/Docs.hs @@ -24,6 +24,9 @@ module Servant.Docs ( -- * 'HasDocs' class and key functions HasDocs(..), docs, pretty, markdown + -- ** Customising generated documentation + , markdownWith, ApiOptions(..), defApiOptions + , requestExamples, responseExamples, ShowContentTypes(..) -- * Generating docs with extra information , docsWith, docsWithIntros, docsWithOptions , ExtraInfo(..), extraInfo diff --git a/servant-docs/src/Servant/Docs/Internal.hs b/servant-docs/src/Servant/Docs/Internal.hs index 8c064d67..5fa72f4c 100644 --- a/servant-docs/src/Servant/Docs/Internal.hs +++ b/servant-docs/src/Servant/Docs/Internal.hs @@ -20,24 +20,31 @@ #include "overlapping-compat.h" module Servant.Docs.Internal where -import Prelude () +import Prelude () import Prelude.Compat + import Control.Applicative 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 Data.ByteString.Lazy.Char8 (ByteString) import qualified Data.ByteString.Char8 as BSC +import Data.ByteString.Lazy.Char8 (ByteString) import qualified Data.CaseInsensitive as CI +import Data.Foldable (fold) import Data.Hashable (Hashable) import Data.HashMap.Strict (HashMap) 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.Monoid (All (..), Any (..), Sum (..), Product (..), First (..), Last (..), Dual (..)) -import Data.Semigroup (Semigroup (..)) +import Data.Monoid (All (..), Any (..), Dual (..), + First (..), Last (..), + Product (..), Sum (..)) 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.Text (Text, unpack) import GHC.Generics @@ -291,6 +298,23 @@ defAction = single :: Endpoint -> Action -> API 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 makeLenses ''DocAuthentication makeLenses ''DocOptions @@ -302,6 +326,7 @@ makeLenses ''DocIntro makeLenses ''DocNote makeLenses ''Response makeLenses ''Action +makeLenses ''ApiOptions -- | Generate the docs for a given API that implements 'HasDocs'. This is the -- default way to create documentation. @@ -518,7 +543,10 @@ class ToAuthInfo a where -- | Generate documentation in Markdown format for -- the given 'API'. markdown :: API -> String -markdown api = unlines $ +markdown = markdownWith defApiOptions + +markdownWith :: ApiOptions -> API -> String +markdownWith ApiOptions{..} api = unlines $ introsStr (api ^. apiIntros) ++ (concatMap (uncurry printEndpoint) . sort . HM.toList $ api ^. apiEndpoints) @@ -632,19 +660,34 @@ markdown api = unlines $ rqbodyStr types s = ["#### Request:", ""] <> formatTypes types - <> formatBodies s + <> formatBodies _requestExamples s formatTypes [] = [] formatTypes ts = ["- Supported content types are:", ""] <> map (\t -> " - `" <> show t <> "`") ts <> [""] - formatBodies :: [(Text, M.MediaType, ByteString)] -> [String] - formatBodies = concatMap formatBody + -- This assumes that when the bodies are created, identical + -- 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) = - "- " <> cs t <> " (`" <> cs (show m) <> "`):" : - contentStr m b + select = case ex of + AllContentTypes -> id + 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 = case (M.mainType mime_type, M.subType mime_type) of @@ -679,7 +722,10 @@ markdown api = unlines $ [] -> ["- No response body\n"] [("", t, r)] -> "- Response body as below." : contentStr t r 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 From 354bee8d325da042c651524ee8600a29689fe106 Mon Sep 17 00:00:00 2001 From: Ivan Lazar Miljenovic Date: Fri, 6 Oct 2017 10:54:55 +1100 Subject: [PATCH 05/14] Remove unused uncurry3 function --- servant-docs/src/Servant/Docs/Internal.hs | 3 --- 1 file changed, 3 deletions(-) diff --git a/servant-docs/src/Servant/Docs/Internal.hs b/servant-docs/src/Servant/Docs/Internal.hs index 5fa72f4c..d17c0a87 100644 --- a/servant-docs/src/Servant/Docs/Internal.hs +++ b/servant-docs/src/Servant/Docs/Internal.hs @@ -724,9 +724,6 @@ markdownWith ApiOptions{..} api = unlines $ xs -> formatBodies _responseExamples xs -uncurry3 :: (a -> b -> c -> d) -> (a,b,c) -> d -uncurry3 f (a,b,c) = f a b c - -- * Instances -- | The generated docs for @a ':<|>' b@ just appends the docs From e3c1c2b46418697f9beda987c63e798a169a6bc7 Mon Sep 17 00:00:00 2001 From: Ivan Lazar Miljenovic Date: Fri, 6 Oct 2017 10:55:50 +1100 Subject: [PATCH 06/14] Fix building on GHC 7.8.4 --- servant-docs/servant-docs.cabal | 2 +- stack-ghc-7.8.4.yaml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/servant-docs/servant-docs.cabal b/servant-docs/servant-docs.cabal index 4498be5e..56515712 100644 --- a/servant-docs/servant-docs.cabal +++ b/servant-docs/servant-docs.cabal @@ -49,7 +49,7 @@ library , control-monad-omega == 0.3.* if !impl(ghc >= 8.0) build-depends: - semigroups >=0.16.2.2 && <0.19 + semigroups >=0.17 && <0.19 hs-source-dirs: src default-language: Haskell2010 ghc-options: -Wall diff --git a/stack-ghc-7.8.4.yaml b/stack-ghc-7.8.4.yaml index 97049725..5ccd7069 100644 --- a/stack-ghc-7.8.4.yaml +++ b/stack-ghc-7.8.4.yaml @@ -23,6 +23,7 @@ extra-deps: - http-client-0.4.30 - natural-transformation-0.4 - primitive-0.6.1.0 +- semigroups-0.17 - servant-js-0.9.3 - should-not-typecheck-2.1.0 - time-locale-compat-0.1.1.1 From a6936480449bb22ed0c8aca6a54f4f5d1ebb6b95 Mon Sep 17 00:00:00 2001 From: Ivan Lazar Miljenovic Date: Fri, 6 Oct 2017 10:59:00 +1100 Subject: [PATCH 07/14] A better name for ApiOptions It's more about how rendering happens than options for the API. --- servant-docs/src/Servant/Docs.hs | 2 +- servant-docs/src/Servant/Docs/Internal.hs | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/servant-docs/src/Servant/Docs.hs b/servant-docs/src/Servant/Docs.hs index 5bf759e3..5d7661fe 100644 --- a/servant-docs/src/Servant/Docs.hs +++ b/servant-docs/src/Servant/Docs.hs @@ -25,7 +25,7 @@ module Servant.Docs ( -- * 'HasDocs' class and key functions HasDocs(..), docs, pretty, markdown -- ** Customising generated documentation - , markdownWith, ApiOptions(..), defApiOptions + , markdownWith, RenderingOptions(..), defRenderingOptions , requestExamples, responseExamples, ShowContentTypes(..) -- * Generating docs with extra information , docsWith, docsWithIntros, docsWithOptions diff --git a/servant-docs/src/Servant/Docs/Internal.hs b/servant-docs/src/Servant/Docs/Internal.hs index d17c0a87..796d39a6 100644 --- a/servant-docs/src/Servant/Docs/Internal.hs +++ b/servant-docs/src/Servant/Docs/Internal.hs @@ -303,14 +303,14 @@ data ShowContentTypes = AllContentTypes | FirstContentType deriving (Eq, Ord, Show, Read, Bounded, Enum) -- | Customise how an 'API' is converted into documentation. -data ApiOptions = ApiOptions +data RenderingOptions = RenderingOptions { _requestExamples :: !ShowContentTypes , _responseExamples :: !ShowContentTypes } deriving (Show) -- | Default API generation options. -defApiOptions :: ApiOptions -defApiOptions = ApiOptions +defRenderingOptions :: RenderingOptions +defRenderingOptions = RenderingOptions { _requestExamples = AllContentTypes , _responseExamples = AllContentTypes } @@ -326,7 +326,7 @@ makeLenses ''DocIntro makeLenses ''DocNote makeLenses ''Response makeLenses ''Action -makeLenses ''ApiOptions +makeLenses ''RenderingOptions -- | Generate the docs for a given API that implements 'HasDocs'. This is the -- default way to create documentation. @@ -543,10 +543,10 @@ class ToAuthInfo a where -- | Generate documentation in Markdown format for -- the given 'API'. markdown :: API -> String -markdown = markdownWith defApiOptions +markdown = markdownWith defRenderingOptions -markdownWith :: ApiOptions -> API -> String -markdownWith ApiOptions{..} api = unlines $ +markdownWith :: RenderingOptions -> API -> String +markdownWith RenderingOptions{..} api = unlines $ introsStr (api ^. apiIntros) ++ (concatMap (uncurry printEndpoint) . sort . HM.toList $ api ^. apiEndpoints) From ddcb3560e0aa2cd6b6166b8fdb8d35076d531132 Mon Sep 17 00:00:00 2001 From: Ivan Lazar Miljenovic Date: Fri, 6 Oct 2017 11:11:59 +1100 Subject: [PATCH 08/14] Better haddocks for new documentation --- servant-docs/src/Servant/Docs/Internal.hs | 39 +++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/servant-docs/src/Servant/Docs/Internal.hs b/servant-docs/src/Servant/Docs/Internal.hs index 796d39a6..adfe3521 100644 --- a/servant-docs/src/Servant/Docs/Internal.hs +++ b/servant-docs/src/Servant/Docs/Internal.hs @@ -298,17 +298,29 @@ defAction = single :: Endpoint -> Action -> API single e a = API mempty (HM.singleton e a) --- | How many examples should be shown? -data ShowContentTypes = AllContentTypes | FirstContentType +-- | How many content-types for each example should be shown? +-- +-- @since 0.11.1 +data ShowContentTypes = AllContentTypes -- ^ For each example, show each content type. + | FirstContentType -- ^ For each example, show only one content type. deriving (Eq, Ord, Show, Read, Bounded, Enum) -- | Customise how an 'API' is converted into documentation. +-- +-- @since 0.11.1 data RenderingOptions = RenderingOptions { _requestExamples :: !ShowContentTypes + -- ^ How many content types to display for request body examples? , _responseExamples :: !ShowContentTypes + -- ^ How many content types to display for response body examples? } deriving (Show) -- | Default API generation options. +-- +-- All content types are shown for both 'requestExamples' and +-- 'responseExamples'. +-- +-- @since 0.11.1 defRenderingOptions :: RenderingOptions defRenderingOptions = RenderingOptions { _requestExamples = AllContentTypes @@ -542,9 +554,32 @@ class ToAuthInfo a where -- | Generate documentation in Markdown format for -- the given 'API'. +-- +-- This is equivalent to @'markdownWith' 'defRenderingOptions'@. markdown :: API -> String markdown = markdownWith defRenderingOptions +-- | Generate documentation in Markdown format for +-- the given 'API' using the specified options. +-- +-- These options allow you to customise aspects such as: +-- +-- * Choose how many content-types for each request body example are +-- shown with 'requestExamples'. +-- +-- * Choose how many content-types for each response body example +-- are shown with 'responseExamples'. +-- +-- For example, to only show the first content-type of each example: +-- +-- @ +-- markdownWith ('defRenderingOptions' +-- & 'requestExamples' '.~' 'FirstContentType' +-- & 'responseExamples' '.~' 'FirstContentType' ) +-- myAPI +-- @ +-- +-- @since 0.11.1 markdownWith :: RenderingOptions -> API -> String markdownWith RenderingOptions{..} api = unlines $ introsStr (api ^. apiIntros) From 0160f9c6d92e24f048d89ebeffcac6f516768c54 Mon Sep 17 00:00:00 2001 From: Ivan Lazar Miljenovic Date: Fri, 6 Oct 2017 11:14:03 +1100 Subject: [PATCH 09/14] Update ChangeLog --- servant-docs/CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/servant-docs/CHANGELOG.md b/servant-docs/CHANGELOG.md index 1573e31f..c77af0a9 100644 --- a/servant-docs/CHANGELOG.md +++ b/servant-docs/CHANGELOG.md @@ -7,6 +7,8 @@ * Document the HTTP Method the parameters of an endpoint belong to (rather than assuming `GET` for all of them). * Content type of sample response body is also displayed. +* Can now control how many content-types for each example are shown + with `markdownWith` and `RenderingOptions`. 0.11 ---- From a21beadf86408aa84859543ec7b4b52d17fbdeca Mon Sep 17 00:00:00 2001 From: Ivan Lazar Miljenovic Date: Fri, 6 Oct 2017 13:03:27 +1100 Subject: [PATCH 10/14] Continuing fix to build on 7.8.4 --- stack-ghc-7.8.4.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/stack-ghc-7.8.4.yaml b/stack-ghc-7.8.4.yaml index 5ccd7069..b3b66e88 100644 --- a/stack-ghc-7.8.4.yaml +++ b/stack-ghc-7.8.4.yaml @@ -28,5 +28,6 @@ extra-deps: - should-not-typecheck-2.1.0 - time-locale-compat-0.1.1.1 - uri-bytestring-0.2.2.0 +- void-0.7.1 - wai-app-static-3.1.5 resolver: lts-2.22 From ed3ace3066824c5c9485954311dc58233dc14fe6 Mon Sep 17 00:00:00 2001 From: Ivan Lazar Miljenovic Date: Fri, 6 Oct 2017 13:19:04 +1100 Subject: [PATCH 11/14] Handle request/response bodies with empty titles --- servant-docs/src/Servant/Docs/Internal.hs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/servant-docs/src/Servant/Docs/Internal.hs b/servant-docs/src/Servant/Docs/Internal.hs index adfe3521..e4bae223 100644 --- a/servant-docs/src/Servant/Docs/Internal.hs +++ b/servant-docs/src/Servant/Docs/Internal.hs @@ -719,11 +719,15 @@ markdownWith RenderingOptions{..} api = unlines $ formatBody :: (Text, NonEmpty M.MediaType, ByteString) -> [String] formatBody (t, ms, b) = - "- " <> cs t <> " (" <> mediaList ms <> "):" : + "- " <> title <> " (" <> mediaList ms <> "):" : contentStr (NE.head ms) b where mediaList = fold . NE.intersperse ", " . fmap (\m -> "`" ++ show m ++ "`") + title + | T.null t = "Example" + | otherwise = cs t + markdownForType mime_type = case (M.mainType mime_type, M.subType mime_type) of ("text", "html") -> "html" From 02821e326a5907bb55a4bf30ee646947a5c9b876 Mon Sep 17 00:00:00 2001 From: Ivan Lazar Miljenovic Date: Fri, 6 Oct 2017 13:21:20 +1100 Subject: [PATCH 12/14] Update sample servant-docs output in tutorial Not all of this is due to the changes in multiple content-type handling. --- doc/tutorial/Docs.lhs | 52 +++++++++++++++++++++---------------------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/doc/tutorial/Docs.lhs b/doc/tutorial/Docs.lhs index 1d428698..2965b323 100644 --- a/doc/tutorial/Docs.lhs +++ b/doc/tutorial/Docs.lhs @@ -110,12 +110,6 @@ markdown :: API -> String That lets us see what our API docs look down in markdown, by looking at `markdown apiDocs`. ````````` text -## Welcome - -This is our super webservice's API. - -Enjoy! - ## GET /hello #### GET Parameters: @@ -132,19 +126,20 @@ Enjoy! - Supported content types are: + - `application/json;charset=utf-8` - `application/json` -- When a value is provided for 'name' +- When a value is provided for 'name' (`application/json;charset=utf-8`, `application/json`): - ```javascript - {"msg":"Hello, Alp"} - ``` + ```javascript +{"msg":"Hello, Alp"} + ``` -- When 'name' is not specified +- When 'name' is not specified (`application/json;charset=utf-8`, `application/json`): - ```javascript - {"msg":"Hello, anonymous coward"} - ``` + ```javascript +{"msg":"Hello, anonymous coward"} + ``` ## POST /marketing @@ -152,28 +147,30 @@ Enjoy! - Supported content types are: + - `application/json;charset=utf-8` - `application/json` -- Example: `application/json` +- Example (`application/json;charset=utf-8`, `application/json`): - ```javascript - {"email":"alp@foo.com","interested_in":["haskell","mathematics"],"age":26,"name":"Alp"} - ``` + ```javascript +{"clientAge":26,"clientEmail":"alp@foo.com","clientName":"Alp","clientInterestedIn":["haskell","mathematics"]} + ``` #### Response: -- Status code 201 +- Status code 200 - Headers: [] - Supported content types are: + - `application/json;charset=utf-8` - `application/json` -- Response body as below. +- Example (`application/json;charset=utf-8`, `application/json`): - ```javascript - {"subject":"Hey Alp, we miss you!","body":"Hi Alp,\n\nSince you've recently turned 26, have you checked out our latest haskell, mathematics products? Give us a visit!","to":"alp@foo.com","from":"great@company.com"} - ``` + ```javascript +{"subject":"Hey Alp, we miss you!","body":"Hi Alp,\n\nSince you've recently turned 26, have you checked out our latest haskell, mathematics products? Give us a visit!","to":"alp@foo.com","from":"great@company.com"} + ``` ## GET /position/:x/:y @@ -189,13 +186,14 @@ Enjoy! - Supported content types are: + - `application/json;charset=utf-8` - `application/json` -- Response body as below. +- Example (`application/json;charset=utf-8`, `application/json`): - ```javascript - {"x":3,"y":14} - ``` + ```javascript +{"yCoord":14,"xCoord":3} + ```` ````````` From dc40badb7c37920334a09d8e921641a3e139ecfd Mon Sep 17 00:00:00 2001 From: Ivan Lazar Miljenovic Date: Fri, 6 Oct 2017 13:38:37 +1100 Subject: [PATCH 13/14] Add motivating example for `markdownWith` to tutorial --- doc/tutorial/Docs.lhs | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/doc/tutorial/Docs.lhs b/doc/tutorial/Docs.lhs index 2965b323..158b3ef9 100644 --- a/doc/tutorial/Docs.lhs +++ b/doc/tutorial/Docs.lhs @@ -211,6 +211,46 @@ docsBS = encodeUtf8 `docsWithIntros` just takes an additional parameter, a list of `DocIntro`s that must be displayed before any endpoint docs. +More customisation can be done with the `markdownWith` function, which allows customising some of the parameters used when generating Markdown. The most obvious of these is how to handle when a request or response body has multiple content types. For example, if we make a slight change to the `/marketing` endpoint of our API so that the request body can also be encoded as a form: + +``` haskell +type ExampleAPI2 = "position" :> Capture "x" Int :> Capture "y" Int :> Get '[JSON] Position + :<|> "hello" :> QueryParam "name" String :> Get '[JSON] HelloMessage + :<|> "marketing" :> ReqBody '[JSON, FormUrlEncoded] ClientInfo :> Post '[JSON] Email + +exampleAPI2 :: Proxy ExampleAPI2 +exampleAPI2 = Proxy + +api2Docs :: API +api2Docs = docs exampleAPI2 +``` + +The relevant output of `markdown api2Docs` is now: + +```````` text +#### Request: + +- Supported content types are: + + - `application/json;charset=utf-8` + - `application/json` + - `application/x-www-form-urlencoded` + +- Example (`application/json;charset=utf-8`, `application/json`): + + ```javascript +{"clientAge":26,"clientEmail":"alp@foo.com","clientName":"Alp","clientInterestedIn":["haskell","mathematics"]} + ``` + +- Example (`application/x-www-form-urlencoded`): + + ``` +clientAge=26&clientEmail=alp%40foo.com&clientName=Alp&clientInterestedIn=haskell&clientInterestedIn=mathematics + ``` +```````` + +If, however, you don't want the extra example encoding shown, then you can use `markdownWith (defRenderingOptions & requestExamples .~ FirstContentType)` to get behaviour identical to `markdown apiDocs`. + We can now serve the API *and* the API docs with a simple server. ``` haskell From b3e35fbc79ec315179f12d1bcfe0e4b2c939c98d Mon Sep 17 00:00:00 2001 From: Ivan Lazar Miljenovic Date: Mon, 9 Oct 2017 13:22:54 +1100 Subject: [PATCH 14/14] Make sure tutorial can build --- doc/tutorial/Docs.lhs | 4 ++++ doc/tutorial/tutorial.cabal | 1 + 2 files changed, 5 insertions(+) diff --git a/doc/tutorial/Docs.lhs b/doc/tutorial/Docs.lhs index 158b3ef9..91c93c71 100644 --- a/doc/tutorial/Docs.lhs +++ b/doc/tutorial/Docs.lhs @@ -23,6 +23,7 @@ import Network.Wai import Servant.API import Servant.Docs import Servant.Server +import Web.FormUrlEncoded(FromForm(..), ToForm(..)) ``` And we'll import some things from one of our earlier modules @@ -218,6 +219,9 @@ type ExampleAPI2 = "position" :> Capture "x" Int :> Capture "y" Int :> Get '[JSO :<|> "hello" :> QueryParam "name" String :> Get '[JSON] HelloMessage :<|> "marketing" :> ReqBody '[JSON, FormUrlEncoded] ClientInfo :> Post '[JSON] Email +instance ToForm ClientInfo +instance FromForm ClientInfo + exampleAPI2 :: Proxy ExampleAPI2 exampleAPI2 = Proxy diff --git a/doc/tutorial/tutorial.cabal b/doc/tutorial/tutorial.cabal index 69d0b0bf..8c23a49d 100644 --- a/doc/tutorial/tutorial.cabal +++ b/doc/tutorial/tutorial.cabal @@ -31,6 +31,7 @@ library , servant-docs == 0.11.* , servant-js >= 0.9 && <0.10 , warp + , http-api-data , http-media , lucid , time