@@ -1,4 +1,3 @@
2{-# LANGUAGE ConstraintKinds #-}
1{-# LANGUAGE ConstraintKinds #-}
3{-# LANGUAGE DataKinds #-}
2{-# LANGUAGE DataKinds #-}
4{-# LANGUAGE DefaultSignatures #-}
3{-# LANGUAGE DefaultSignatures #-}
@@ -17,7 +16,6 @@
17{-# LANGUAGE TypeOperators #-}
16{-# LANGUAGE TypeOperators #-}
18{-# LANGUAGE UndecidableInstances #-}
17{-# LANGUAGE UndecidableInstances #-}
20−#include "overlapping-compat.h"
21module Servant.Docs.Internal where
19module Servant.Docs.Internal where
23import Prelude ()
21import Prelude ()
@@ -27,15 +25,14 @@ import Control.Applicative
27import Control.Arrow
25import Control.Arrow
29import Control.Lens
27import Control.Lens
30− (makeLenses, mapped, over, traversed, view, (%~), (&), (.~),
28+ (makeLenses, mapped, each, over, set, to, toListOf, traversed, view,
31− (<>~), (^.), (|>))
29+ _1, (%~), (&), (.~), (<>~), (^.), (|>))
32−import qualified Control.Monad.Omega as Omega
33import qualified Data.ByteString.Char8 as BSC
30import qualified Data.ByteString.Char8 as BSC
34import Data.ByteString.Lazy.Char8
31import Data.ByteString.Lazy.Char8
35 (ByteString)
32 (ByteString)
36import qualified Data.CaseInsensitive as CI
33import qualified Data.CaseInsensitive as CI
37import Data.Foldable
34import Data.Foldable
38− (fold)
35+ (fold, toList)
39import Data.Hashable
36import Data.Hashable
40 (Hashable)
37 (Hashable)
41import Data.HashMap.Strict
38import Data.HashMap.Strict
@@ -53,17 +50,21 @@ import Data.Ord
53 (comparing)
50 (comparing)
54import Data.Proxy
51import Data.Proxy
55 (Proxy (Proxy))
52 (Proxy (Proxy))
58import Data.String.Conversions
53import Data.String.Conversions
60import Data.Text
55import Data.Text
61 (Text, unpack)
56 (Text, unpack)
62import GHC.Generics
57import GHC.Generics
58+ (Generic, Rep, K1(K1), M1(M1), U1(U1), V1,
59+ (:*:)((:*:)), (:+:)(L1, R1))
60+import qualified GHC.Generics as G
63import GHC.TypeLits
61import GHC.TypeLits
64import Servant.API
62import Servant.API
65import Servant.API.ContentTypes
63import Servant.API.ContentTypes
66import Servant.API.TypeLevel
64import Servant.API.TypeLevel
65+import Servant.API.Generic
67+import qualified Data.Universe.Helpers as U
68import qualified Data.HashMap.Strict as HM
69import qualified Data.HashMap.Strict as HM
69import qualified Data.Text as T
70import qualified Data.Text as T
@@ -76,14 +77,15 @@ import qualified Network.HTTP.Types as HTTP
76-- or any 'Endpoint' value you want using the 'path' and 'method'
77-- or any 'Endpoint' value you want using the 'path' and 'method'
77-- lenses to tweak.
78-- lenses to tweak.
80−-- λ> 'defEndpoint'
80+-- >>> defEndpoint
82−-- λ> 'defEndpoint' & 'path' '<>~' ["foo"]
83+-- >>> defEndpoint & path <>~ ["foo"]
83−-- GET /foo
84+-- "GET" /foo
84−-- λ> 'defEndpoint' & 'path' '<>~' ["foo"] & 'method' '.~' 'HTTP.methodPost'
86+-- >>> defEndpoint & path <>~ ["foo"] & method .~ HTTP.methodPost
85−-- POST /foo
87+-- "POST" /foo
87data Endpoint = Endpoint
89data Endpoint = Endpoint
88 { _path :: [String] -- type collected
90 { _path :: [String] -- type collected
89 , _method :: HTTP.Method -- type collected
91 , _method :: HTTP.Method -- type collected
@@ -104,14 +106,15 @@ showPath ps = concatMap ('/' :) ps
105-- Here's how you can modify it:
107-- Here's how you can modify it:
108−-- λ> 'defEndpoint'
109+-- >>> defEndpoint
109−-- GET /
110+-- "GET" /
110−-- λ> 'defEndpoint' & 'path' '<>~' ["foo"]
112+-- >>> defEndpoint & path <>~ ["foo"]
111−-- GET /foo
113+-- "GET" /foo
112−-- λ> 'defEndpoint' & 'path' '<>~' ["foo"] & 'method' '.~' 'HTTP.methodPost'
115+-- >>> defEndpoint & path <>~ ["foo"] & method .~ HTTP.methodPost
113−-- POST /foo
116+-- "POST" /foo
115defEndpoint :: Endpoint
118defEndpoint :: Endpoint
116defEndpoint = Endpoint [] HTTP.methodGet
119defEndpoint = Endpoint [] HTTP.methodGet
@@ -128,7 +131,8 @@ instance Semigroup API where
128 (<>) = mappend
131 (<>) = mappend
130instance Monoid API where
133instance Monoid API where
131− API a1 b1 `mappend` API a2 b2 = API (a1 `mappend` a2) (b1 `mappend` b2)
134+ API a1 b1 `mappend` API a2 b2 = API (a1 `mappend` a2)
135+ (HM.unionWith combineAction b1 b2)
132 mempty = API mempty mempty
136 mempty = API mempty mempty
134-- | An empty 'API'
138-- | An empty 'API'
@@ -157,6 +161,20 @@ data DocQueryParam = DocQueryParam
157 , _paramKind :: ParamKind
161 , _paramKind :: ParamKind
158 } deriving (Eq, Ord, Show)
162 } deriving (Eq, Ord, Show)
164+-- | A type to represent fragment. Holds the name of the fragment and its description.
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)
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
160-- | An introductory paragraph for your documentation. You can pass these to
178-- | An introductory paragraph for your documentation. You can pass these to
161-- 'docsWithIntros'.
179-- 'docsWithIntros'.
162data DocIntro = DocIntro
180data DocIntro = DocIntro
@@ -222,12 +240,14 @@ data ParamKind = Normal | List | Flag
222-- want to write a 'ToSample' instance for the type that'll be represented
240-- want to write a 'ToSample' instance for the type that'll be represented
223-- as encoded data in the response.
241-- as encoded data in the response.
225−-- Can be tweaked with three lenses.
243+-- Can be tweaked with four lenses.
246+-- Response {_respStatus = 200, _respTypes = [], _respBody = [], _respHeaders = []}
248+-- >>> defResponse & respStatus .~ 204 & respBody .~ [("If everything goes well", "application/json", "{ \"status\": \"ok\" }")]
249+-- Response {_respStatus = 204, _respTypes = [], _respBody = [("If everything goes well",application/json,"{ \"status\": \"ok\" }")], _respHeaders = []}
228−-- > Response {_respStatus = 200, _respTypes = [], _respBody = []}
229−-- > λ> defResponse & respStatus .~ 204 & respBody .~ [("If everything goes well", "{ \"status\": \"ok\" }")]
230−-- > Response {_respStatus = 204, _respTypes = [], _respBody = [("If everything goes well", "{ \"status\": \"ok\" }")]}
231data Response = Response
251data Response = Response
232 { _respStatus :: Int
252 { _respStatus :: Int
233 , _respTypes :: [M.MediaType]
253 , _respTypes :: [M.MediaType]
@@ -235,14 +255,25 @@ data Response = Response
235 , _respHeaders :: [HTTP.Header]
255 , _respHeaders :: [HTTP.Header]
236 } deriving (Eq, Ord, Show)
256 } deriving (Eq, Ord, Show)
258+-- | Combine two Responses, we can't make a monoid because merging Status breaks
261+-- As such, we invent a non-commutative, left associative operation
262+-- 'combineResponse' to mush two together taking the status from the very left.
263+combineResponse :: Response -> Response -> Response
264+Response s ts bs hs `combineResponse` Response _ ts' bs' hs'
265+ = Response s (ts <> ts') (bs <> bs') (hs <> hs')
238-- | Default response: status code 200, no response body.
267-- | Default response: status code 200, no response body.
240−-- Can be tweaked with two lenses.
269+-- Can be tweaked with four lenses.
272+-- Response {_respStatus = 200, _respTypes = [], _respBody = [], _respHeaders = []}
274+-- >>> defResponse & respStatus .~ 204
275+-- Response {_respStatus = 204, _respTypes = [], _respBody = [], _respHeaders = []}
243−-- > Response {_respStatus = 200, _respBody = Nothing}
244−-- > λ> defResponse & respStatus .~ 204 & respBody .~ Just "[]"
245−-- > Response {_respStatus = 204, _respBody = Just "[]"}
246defResponse :: Response
277defResponse :: Response
247defResponse = Response
278defResponse = Response
248 { _respStatus = 200
279 { _respStatus = 200
@@ -264,8 +295,9 @@ defResponse = Response
264data Action = Action
295data Action = Action
265 { _authInfo :: [DocAuthentication] -- user supplied info
296 { _authInfo :: [DocAuthentication] -- user supplied info
266 , _captures :: [DocCapture] -- type collected + user supplied info
297 , _captures :: [DocCapture] -- type collected + user supplied info
267− , _headers :: [Text] -- type collected
298+ , _headers :: [HTTP.Header] -- type collected
268 , _params :: [DocQueryParam] -- type collected + user supplied info
299 , _params :: [DocQueryParam] -- type collected + user supplied info
300+ , _fragment :: Maybe DocFragment -- type collected + user supplied info
269 , _notes :: [DocNote] -- user supplied
301 , _notes :: [DocNote] -- user supplied
270 , _mxParams :: [(String, [DocQueryParam])] -- type collected + user supplied info
302 , _mxParams :: [(String, [DocQueryParam])] -- type collected + user supplied info
271 , _rqtypes :: [M.MediaType] -- type collected
303 , _rqtypes :: [M.MediaType] -- type collected
@@ -277,27 +309,30 @@ data Action = Action
279-- As such, we invent a non-commutative, left associative operation
311-- As such, we invent a non-commutative, left associative operation
280−-- 'combineAction' to mush two together taking the response, body and content
312+-- 'combineAction' to mush two together taking the response from the very left.
281−-- types from the very left.
282combineAction :: Action -> Action -> Action
313combineAction :: Action -> Action -> Action
283−Action a c h p n m ts body resp `combineAction` Action a' c' h' p' n' m' _ _ _ =
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' =
284− Action (a <> a') (c <> c') (h <> h') (p <> p') (n <> n') (m <> 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')
286-- | Default 'Action'. Has no 'captures', no query 'params', expects
318-- | Default 'Action'. Has no 'captures', no query 'params', expects
287-- no request body ('rqbody') and the typical response is 'defResponse'.
319-- no request body ('rqbody') and the typical response is 'defResponse'.
289-- Tweakable with lenses.
321-- Tweakable with lenses.
291−-- > λ> defAction
323+-- >>> defAction
292−-- > Action {_captures = [], _headers = [], _params = [], _mxParams = [], _rqbody = Nothing, _response = Response {_respStatus = 200, _respBody = Nothing}}
324+-- Action {_authInfo = [], _captures = [], _headers = [], _params = [], _fragment = Nothing, _notes = [], _mxParams = [], _rqtypes = [], _rqbody = [], _response = Response {_respStatus = 200, _respTypes = [], _respBody = [], _respHeaders = []}}
293−-- > λ> defAction & response.respStatus .~ 201
326+-- >>> defAction & response.respStatus .~ 201
294−-- > Action {_captures = [], _headers = [], _params = [], _mxParams = [], _rqbody = Nothing, _response = Response {_respStatus = 201, _respBody = Nothing}}
327+-- Action {_authInfo = [], _captures = [], _headers = [], _params = [], _fragment = Nothing, _notes = [], _mxParams = [], _rqtypes = [], _rqbody = [], _response = Response {_respStatus = 201, _respTypes = [], _respBody = [], _respHeaders = []}}
295defAction :: Action
329defAction :: Action
296defAction =
330defAction =
297 Action []
331 Action []
@@ -321,12 +356,14 @@ data ShowContentTypes = AllContentTypes -- ^ For each example, show each conten
322-- @since 0.11.1
357-- @since 0.11.1
323data RenderingOptions = RenderingOptions
358data RenderingOptions = RenderingOptions
324− { _requestExamples :: !ShowContentTypes
359+ { _requestExamples :: !ShowContentTypes
325 -- ^ How many content types to display for request body examples?
360 -- ^ How many content types to display for request body examples?
326− , _responseExamples :: !ShowContentTypes
361+ , _responseExamples :: !ShowContentTypes
327 -- ^ How many content types to display for response body examples?
362 -- ^ How many content types to display for response body examples?
328− , _notesHeading :: !(Maybe String)
363+ , _notesHeading :: !(Maybe String)
329 -- ^ Optionally group all 'notes' together under a common heading.
364 -- ^ Optionally group all 'notes' together under a common heading.
365+ , _renderCurlBasePath :: !(Maybe String)
366+ -- ^ Optionally render example curl requests under a common base path (e.g. `http://localhost:80`).
330 } deriving (Show)
367 } deriving (Show)
332-- | Default API generation options.
369-- | Default API generation options.
@@ -338,9 +375,10 @@ data RenderingOptions = RenderingOptions
338-- @since 0.11.1
375-- @since 0.11.1
339defRenderingOptions :: RenderingOptions
376defRenderingOptions :: RenderingOptions
340defRenderingOptions = RenderingOptions
377defRenderingOptions = RenderingOptions
341− { _requestExamples = AllContentTypes
378+ { _requestExamples = AllContentTypes
342− , _responseExamples = AllContentTypes
379+ , _responseExamples = AllContentTypes
343− , _notesHeading = Nothing
380+ , _notesHeading = Nothing
381+ , _renderCurlBasePath = Nothing
346-- gimme some lenses
384-- gimme some lenses
@@ -350,6 +388,7 @@ makeLenses ''API
350makeLenses ''Endpoint
388makeLenses ''Endpoint
351makeLenses ''DocCapture
389makeLenses ''DocCapture
352makeLenses ''DocQueryParam
390makeLenses ''DocQueryParam
391+makeLenses ''DocFragment
353makeLenses ''DocIntro
392makeLenses ''DocIntro
354makeLenses ''DocNote
393makeLenses ''DocNote
355makeLenses ''Response
394makeLenses ''Response
@@ -359,7 +398,8 @@ makeLenses ''RenderingOptions
359-- | Generate the docs for a given API that implements 'HasDocs'. This is the
398-- | Generate the docs for a given API that implements 'HasDocs'. This is the
360-- default way to create documentation.
399-- default way to create documentation.
362−-- prop> docs == docsWithOptions defaultDocOptions
401+-- > docs == docsWithOptions defaultDocOptions
363docs :: HasDocs api => Proxy api -> API
403docs :: HasDocs api => Proxy api -> API
364docs p = docsWithOptions p defaultDocOptions
404docs p = docsWithOptions p defaultDocOptions
@@ -375,9 +415,9 @@ docsWithOptions p = docsFor p (defEndpoint, defAction)
375-- > extra :: ExtraInfo TestApi
415-- > extra :: ExtraInfo TestApi
376-- > extra =
416-- > extra =
377-- > extraInfo (Proxy :: Proxy ("greet" :> Capture "greetid" Text :> Delete)) $
417-- > extraInfo (Proxy :: Proxy ("greet" :> Capture "greetid" Text :> Delete)) $
378−-- > defAction & headers <>~ ["unicorns"]
418+-- > defAction & headers <>~ [("X-Num-Unicorns", 1)]
379-- > & notes <>~ [ DocNote "Title" ["This is some text"]
419-- > & notes <>~ [ DocNote "Title" ["This is some text"]
380−-- > , DocNote "Second secton" ["And some more"]
420+-- > , DocNote "Second section" ["And some more"]
383extraInfo :: (IsIn endpoint api, HasLink endpoint, HasDocs endpoint)
423extraInfo :: (IsIn endpoint api, HasLink endpoint, HasDocs endpoint)
@@ -407,7 +447,7 @@ docsWith opts intros (ExtraInfo endpoints) p =
407 & apiEndpoints %~ HM.unionWith (flip combineAction) endpoints
447 & apiEndpoints %~ HM.unionWith (flip combineAction) endpoints
410−-- | Generate the docs for a given API that implements 'HasDocs' with with any
450+-- | Generate the docs for a given API that implements 'HasDocs' with any
411-- number of introduction(s)
451-- number of introduction(s)
412docsWithIntros :: HasDocs api => [DocIntro] -> Proxy api -> API
452docsWithIntros :: HasDocs api => [DocIntro] -> Proxy api -> API
413docsWithIntros intros = docsWith defaultDocOptions intros mempty
453docsWithIntros intros = docsWith defaultDocOptions intros mempty
@@ -470,22 +510,22 @@ samples = map ("",)
471-- | Default sample Generic-based inputs/outputs.
511-- | Default sample Generic-based inputs/outputs.
472defaultSamples :: forall a. (Generic a, GToSample (Rep a)) => Proxy a -> [(Text, a)]
512defaultSamples :: forall a. (Generic a, GToSample (Rep a)) => Proxy a -> [(Text, a)]
473−defaultSamples _ = Omega.runOmega $ second to <$> gtoSamples (Proxy :: Proxy (Rep a))
513+defaultSamples _ = second G.to <$> gtoSamples (Proxy :: Proxy (Rep a))
475-- | @'ToSample'@ for Generics.
515-- | @'ToSample'@ for Generics.
477−-- The use of @'Omega'@ allows for more productive sample generation.
517+-- Note: we use combinators from "Universe.Data.Helpers" for more productive sample generation.
478class GToSample t where
518class GToSample t where
479− gtoSamples :: proxy t -> Omega.Omega (Text, t x)
519+ gtoSamples :: proxy t -> [(Text, t x)]
481instance GToSample U1 where
521instance GToSample U1 where
482− gtoSamples _ = Omega.each (singleSample U1)
522+ gtoSamples _ = singleSample U1
484instance GToSample V1 where
524instance GToSample V1 where
485 gtoSamples _ = empty
525 gtoSamples _ = empty
487instance (GToSample p, GToSample q) => GToSample (p :*: q) where
527instance (GToSample p, GToSample q) => GToSample (p :*: q) where
488− gtoSamples _ = render <$> ps <*> qs
528+ gtoSamples _ = U.cartesianProduct render ps qs
490 ps = gtoSamples (Proxy :: Proxy p)
530 ps = gtoSamples (Proxy :: Proxy p)
491 qs = gtoSamples (Proxy :: Proxy q)
531 qs = gtoSamples (Proxy :: Proxy q)
@@ -494,13 +534,13 @@ instance (GToSample p, GToSample q) => GToSample (p :*: q) where
494 | otherwise = (ta <> ", " <> tb, a :*: b)
534 | otherwise = (ta <> ", " <> tb, a :*: b)
496instance (GToSample p, GToSample q) => GToSample (p :+: q) where
536instance (GToSample p, GToSample q) => GToSample (p :+: q) where
497− gtoSamples _ = lefts <|> rights
537+ gtoSamples _ = lefts U.+++ rights
499 lefts = second L1 <$> gtoSamples (Proxy :: Proxy p)
539 lefts = second L1 <$> gtoSamples (Proxy :: Proxy p)
500 rights = second R1 <$> gtoSamples (Proxy :: Proxy q)
540 rights = second R1 <$> gtoSamples (Proxy :: Proxy q)
502instance ToSample a => GToSample (K1 i a) where
542instance ToSample a => GToSample (K1 i a) where
503− gtoSamples _ = second K1 <$> Omega.each (toSamples (Proxy :: Proxy a))
543+ gtoSamples _ = second K1 <$> toSamples (Proxy :: Proxy a)
505instance (GToSample f) => GToSample (M1 i a f) where
545instance (GToSample f) => GToSample (M1 i a f) where
506 gtoSamples _ = second M1 <$> gtoSamples (Proxy :: Proxy f)
546 gtoSamples _ = second M1 <$> gtoSamples (Proxy :: Proxy f)
@@ -568,6 +608,15 @@ class ToCapture c where
568class ToAuthInfo a where
608class ToAuthInfo a where
569 toAuthInfo :: Proxy a -> DocAuthentication
609 toAuthInfo :: Proxy a -> DocAuthentication
611+-- | The class that helps us get documentation for URL fragments.
613+-- Example of an instance:
615+-- > instance ToFragment (Fragment a) where
616+-- > toFragment _ = DocFragment "fragment" "fragment description"
617+class ToFragment t where
618+ toFragment :: Proxy t -> DocFragment
571-- | Generate documentation in Markdown format for
620-- | Generate documentation in Markdown format for
572-- the given 'API'.
621-- the given 'API'.
@@ -597,7 +646,7 @@ markdown = markdownWith defRenderingOptions
598-- @since 0.11.1
647-- @since 0.11.1
599markdownWith :: RenderingOptions -> API -> String
648markdownWith :: RenderingOptions -> API -> String
600−markdownWith RenderingOptions{..} api = unlines $
649+markdownWith RenderingOptions{..} api = unlines $
601 introsStr (api ^. apiIntros)
650 introsStr (api ^. apiIntros)
602 ++ (concatMap (uncurry printEndpoint) . sort . HM.toList $ api ^. apiEndpoints)
651 ++ (concatMap (uncurry printEndpoint) . sort . HM.toList $ api ^. apiEndpoints)
@@ -608,10 +657,12 @@ markdownWith RenderingOptions{..} api = unlines $
608 notesStr (action ^. notes) ++
657 notesStr (action ^. notes) ++
609 authStr (action ^. authInfo) ++
658 authStr (action ^. authInfo) ++
610 capturesStr (action ^. captures) ++
659 capturesStr (action ^. captures) ++
611− headersStr (action ^. headers) ++
660+ headersStr (toListOf (headers . each . _1 . to (T.pack . BSC.unpack . CI.original)) action) ++
612 paramsStr meth (action ^. params) ++
661 paramsStr meth (action ^. params) ++
662+ fragmentStr (action ^. fragment) ++
613 rqbodyStr (action ^. rqtypes) (action ^. rqbody) ++
663 rqbodyStr (action ^. rqtypes) (action ^. rqbody) ++
614 responseStr (action ^. response) ++
664 responseStr (action ^. response) ++
665+ maybe [] (curlStr endpoint (action ^. headers) (action ^. rqbody)) _renderCurlBasePath ++
617 where str = "## " ++ BSC.unpack meth
668 where str = "## " ++ BSC.unpack meth
@@ -711,6 +762,14 @@ markdownWith RenderingOptions{..} api = unlines $
712 where values = param ^. paramValues
763 where values = param ^. paramValues
765+ fragmentStr :: Maybe DocFragment -> [String]
766+ fragmentStr Nothing = []
767+ fragmentStr (Just frag) =
768+ [ "### Fragment:", ""
769+ , "- *" ++ (frag ^. fragSymbol) ++ "*: " ++ (frag ^. fragDesc)
714 rqbodyStr :: [M.MediaType] -> [(Text, M.MediaType, ByteString)]-> [String]
773 rqbodyStr :: [M.MediaType] -> [(Text, M.MediaType, ByteString)]-> [String]
715 rqbodyStr [] [] = []
774 rqbodyStr [] [] = []
716 rqbodyStr types s =
775 rqbodyStr types s =
@@ -759,12 +818,11 @@ markdownWith RenderingOptions{..} api = unlines $
759 ("text", "css") -> "css"
818 ("text", "css") -> "css"
760 (_, _) -> ""
819 (_, _) -> ""
763 contentStr mime_type body =
821 contentStr mime_type body =
765− " ```" <> markdownForType mime_type :
823+ "```" <> markdownForType mime_type :
766 cs body :
824 cs body :
767− " ```" :
825+ "```" :
@@ -784,11 +842,41 @@ markdownWith RenderingOptions{..} api = unlines $
785 formatBodies _responseExamples xs
843 formatBodies _responseExamples xs
845+ curlStr :: Endpoint -> [HTTP.Header] -> [(Text, M.MediaType, ByteString)] -> String -> [String]
846+ curlStr endpoint hdrs reqBodies basePath =
847+ [ "### Sample Request:"
850+ , "curl -X" ++ BSC.unpack (endpoint ^. method) ++ " \\"
852+ maybe [] pure mbMediaTypeStr <>
854+ maybe [] pure mbReqBodyStr <>
855+ [ " " ++ basePath ++ showPath (endpoint ^. path)
860+ where escapeQuotes :: String -> String
861+ escapeQuotes = concatMap $ \c -> case c of
864+ mbReqBody = listToMaybe reqBodies
865+ mbMediaTypeStr = mkMediaTypeStr <$> mbReqBody
866+ headersStrs = mkHeaderStr <$> hdrs
867+ mbReqBodyStr = mkReqBodyStr <$> mbReqBody
868+ mkMediaTypeStr (_, media_type, _) =
869+ " -H \"Content-Type: " ++ show media_type ++ "\" \\"
870+ mkHeaderStr (hdrName, hdrVal) =
871+ " -H \"" ++ escapeQuotes (cs (CI.original hdrName)) ++ ": " ++
872+ escapeQuotes (cs hdrVal) ++ "\" \\"
873+ mkReqBodyStr (_, _, body) = " -d \"" ++ escapeQuotes (cs body) ++ "\" \\"
787-- * Instances
875-- * Instances
789-- | The generated docs for @a ':<|>' b@ just appends the docs
877-- | The generated docs for @a ':<|>' b@ just appends the docs
790-- for @a@ with the docs for @b@.
878-- for @a@ with the docs for @b@.
791−instance OVERLAPPABLE_
879+instance {-# OVERLAPPABLE #-}
792 (HasDocs a, HasDocs b)
880 (HasDocs a, HasDocs b)
793 => HasDocs (a :<|> b) where
881 => HasDocs (a :<|> b) where
@@ -807,7 +895,7 @@ instance HasDocs EmptyAPI where
807-- | @"books" :> 'Capture' "isbn" Text@ will appear as
895-- | @"books" :> 'Capture' "isbn" Text@ will appear as
808-- @/books/:isbn@ in the docs.
896-- @/books/:isbn@ in the docs.
809instance (KnownSymbol sym, ToCapture (Capture sym a), HasDocs api)
897instance (KnownSymbol sym, ToCapture (Capture sym a), HasDocs api)
810− => HasDocs (Capture' mods sym a :> api) where
898+ => HasDocs (Capture' '[] sym a :> api) where
812 docsFor Proxy (endpoint, action) =
900 docsFor Proxy (endpoint, action) =
813 docsFor subApiP (endpoint', action')
901 docsFor subApiP (endpoint', action')
@@ -819,6 +907,28 @@ instance (KnownSymbol sym, ToCapture (Capture sym a), HasDocs api)
819 endpoint' = over path (\p -> p ++ [":" ++ symbolVal symP]) endpoint
907 endpoint' = over path (\p -> p ++ [":" ++ symbolVal symP]) endpoint
820 symP = Proxy :: Proxy sym
908 symP = Proxy :: Proxy sym
910+instance (KnownSymbol descr, KnownSymbol sym, HasDocs api)
911+ => HasDocs (Capture' (Description descr ': mods) sym a :> api) where
913+ docsFor Proxy (endpoint, action) =
914+ docsFor subApiP (endpoint', action')
916+ where subApiP = Proxy :: Proxy api
918+ docCapture = DocCapture (symbolVal symP) (symbolVal descrP)
919+ action' = over captures (|> docCapture) action
920+ endpoint' = over path (\p -> p ++ [":" ++ symbolVal symP]) endpoint
921+ descrP = Proxy :: Proxy descr
922+ symP = Proxy :: Proxy sym
924+instance {-# OVERLAPPABLE #-} HasDocs (Capture' mods sym a :> api)
925+ => HasDocs (Capture' (mod ': mods) sym a :> api) where
930+ where apiP = Proxy :: Proxy (Capture' mods sym a :> api)
823-- | @"books" :> 'CaptureAll' "isbn" Text@ will appear as
933-- | @"books" :> 'CaptureAll' "isbn" Text@ will appear as
824-- @/books/:isbn@ in the docs.
934-- @/books/:isbn@ in the docs.
@@ -836,7 +946,7 @@ instance (KnownSymbol sym, ToCapture (CaptureAll sym a), HasDocs sublayout)
836 symP = Proxy :: Proxy sym
946 symP = Proxy :: Proxy sym
839−instance OVERLAPPABLE_
949+instance {-# OVERLAPPABLE #-}
840 (ToSample a, AllMimeRender (ct ': cts) a, KnownNat status
950 (ToSample a, AllMimeRender (ct ': cts) a, KnownNat status
841 , ReflectMethod method)
951 , ReflectMethod method)
842 => HasDocs (Verb method status (ct ': cts) a) where
952 => HasDocs (Verb method status (ct ': cts) a) where
@@ -852,12 +962,25 @@ instance OVERLAPPABLE_
852 status = fromInteger $ natVal (Proxy :: Proxy status)
962 status = fromInteger $ natVal (Proxy :: Proxy status)
853 p = Proxy :: Proxy a
963 p = Proxy :: Proxy a
965+instance (ReflectMethod method) =>
966+ HasDocs (NoContentVerb method) where
967+ docsFor Proxy (endpoint, action) DocOptions{..} =
968+ single endpoint' action'
970+ where endpoint' = endpoint & method .~ method'
971+ action' = action & response.respStatus .~ 204
972+ & response.respTypes .~ []
973+ & response.respBody .~ []
974+ & response.respHeaders .~ []
975+ method' = reflectMethod (Proxy :: Proxy method)
855-- | TODO: mention the endpoint is streaming, its framing strategy
977-- | TODO: mention the endpoint is streaming, its framing strategy
857-- Also there are no samples.
979-- Also there are no samples.
858−instance OVERLAPPABLE_
980+--
859− (MimeRender ct a, KnownNat status
981+-- TODO: AcceptFraming for content-type
982+instance {-# OVERLAPPABLE #-}
860− , ReflectMethod method)
983+ (Accept ct, KnownNat status, ReflectMethod method)
861 => HasDocs (Stream method status framing ct a) where
984 => HasDocs (Stream method status framing ct a) where
862 docsFor Proxy (endpoint, action) DocOptions{..} =
985 docsFor Proxy (endpoint, action) DocOptions{..} =
863 single endpoint' action'
986 single endpoint' action'
@@ -868,9 +991,8 @@ instance OVERLAPPABLE_
868 t = Proxy :: Proxy '[ct]
991 t = Proxy :: Proxy '[ct]
869 method' = reflectMethod (Proxy :: Proxy method)
992 method' = reflectMethod (Proxy :: Proxy method)
870 status = fromInteger $ natVal (Proxy :: Proxy status)
993 status = fromInteger $ natVal (Proxy :: Proxy status)
871− p = Proxy :: Proxy a
873−instance OVERLAPPING_
995+instance {-# OVERLAPPING #-}
874 (ToSample a, AllMimeRender (ct ': cts) a, KnownNat status
996 (ToSample a, AllMimeRender (ct ': cts) a, KnownNat status
875 , ReflectMethod method, AllHeaderSamples ls, GetHeaders (HList ls))
997 , ReflectMethod method, AllHeaderSamples ls, GetHeaders (HList ls))
876 => HasDocs (Verb method status (ct ': cts) (Headers ls a)) where
998 => HasDocs (Verb method status (ct ': cts) (Headers ls a)) where
@@ -888,14 +1010,17 @@ instance OVERLAPPING_
888 status = fromInteger $ natVal (Proxy :: Proxy status)
1010 status = fromInteger $ natVal (Proxy :: Proxy status)
889 p = Proxy :: Proxy a
1011 p = Proxy :: Proxy a
891−instance (KnownSymbol sym, HasDocs api)
1013+instance (ToHttpApiData a, ToSample a, KnownSymbol sym, HasDocs api)
892 => HasDocs (Header' mods sym a :> api) where
1014 => HasDocs (Header' mods sym a :> api) where
893 docsFor Proxy (endpoint, action) =
1015 docsFor Proxy (endpoint, action) =
894 docsFor subApiP (endpoint, action')
1016 docsFor subApiP (endpoint, action')
896 where subApiP = Proxy :: Proxy api
1018 where subApiP = Proxy :: Proxy api
897− action' = over headers (|> headername) action
1019+ action' = over headers (|> (headerName, headerVal)) action
898− headername = T.pack $ symbolVal (Proxy :: Proxy sym)
1020+ headerName = CI.mk . cs $ symbolVal (Proxy :: Proxy sym)
1021+ headerVal = case toSample (Proxy :: Proxy a) of
1022+ Just x -> cs $ toHeader x
1023+ Nothing -> "<no header sample provided>"
900instance (KnownSymbol sym, ToParam (QueryParam' mods sym a), HasDocs api)
1025instance (KnownSymbol sym, ToParam (QueryParam' mods sym a), HasDocs api)
901 => HasDocs (QueryParam' mods sym a :> api) where
1026 => HasDocs (QueryParam' mods sym a :> api) where
@@ -928,6 +1053,15 @@ instance (KnownSymbol sym, ToParam (QueryFlag sym), HasDocs api)
928 paramP = Proxy :: Proxy (QueryFlag sym)
1053 paramP = Proxy :: Proxy (QueryFlag sym)
929 action' = over params (|> toParam paramP) action
1054 action' = over params (|> toParam paramP) action
1056+instance (ToFragment (Fragment a), HasDocs api)
1057+ => HasDocs (Fragment a :> api) where
1059+ docsFor Proxy (endpoint, action) =
1060+ docsFor subApiP (endpoint, action')
1062+ where subApiP = Proxy :: Proxy api
1063+ fragmentP = Proxy :: Proxy (Fragment a)
1064+ action' = set fragment (Just (toFragment fragmentP)) action
932instance HasDocs Raw where
1066instance HasDocs Raw where
933 docsFor _proxy (endpoint, action) _ =
1067 docsFor _proxy (endpoint, action) _ =
@@ -960,7 +1094,6 @@ instance (KnownSymbol desc, HasDocs api)
960-- both are even defined) for any particular type.
1094-- both are even defined) for any particular type.
961instance (ToSample a, AllMimeRender (ct ': cts) a, HasDocs api)
1095instance (ToSample a, AllMimeRender (ct ': cts) a, HasDocs api)
962 => HasDocs (ReqBody' mods (ct ': cts) a :> api) where
1096 => HasDocs (ReqBody' mods (ct ': cts) a :> api) where
964 docsFor Proxy (endpoint, action) opts@DocOptions{..} =
1097 docsFor Proxy (endpoint, action) opts@DocOptions{..} =
965 docsFor subApiP (endpoint, action') opts
1098 docsFor subApiP (endpoint, action') opts
@@ -971,6 +1104,18 @@ instance (ToSample a, AllMimeRender (ct ': cts) a, HasDocs api)
971 t = Proxy :: Proxy (ct ': cts)
1104 t = Proxy :: Proxy (ct ': cts)
972 p = Proxy :: Proxy a
1105 p = Proxy :: Proxy a
1107+-- | TODO: this instance is incomplete.
1108+instance (HasDocs api, Accept ctype) => HasDocs (StreamBody' mods framing ctype a :> api) where
1109+ docsFor Proxy (endpoint, action) opts =
1110+ docsFor subApiP (endpoint, action') opts
1112+ subApiP = Proxy :: Proxy api
1115+ action' = action & rqtypes .~ toList (contentTypes t)
1117+ t = Proxy :: Proxy ctype
974instance (KnownSymbol path, HasDocs api) => HasDocs (path :> api) where
1119instance (KnownSymbol path, HasDocs api) => HasDocs (path :> api) where
976 docsFor Proxy (endpoint, action) =
1121 docsFor Proxy (endpoint, action) =
@@ -999,6 +1144,9 @@ instance HasDocs api => HasDocs (Vault :> api) where
999instance HasDocs api => HasDocs (WithNamedContext name context api) where
1144instance HasDocs api => HasDocs (WithNamedContext name context api) where
1000 docsFor Proxy = docsFor (Proxy :: Proxy api)
1145 docsFor Proxy = docsFor (Proxy :: Proxy api)
1147+instance HasDocs api => HasDocs (WithResource res :> api) where
1148+ docsFor Proxy = docsFor (Proxy :: Proxy api)
1002instance (ToAuthInfo (BasicAuth realm usr), HasDocs api) => HasDocs (BasicAuth realm usr :> api) where
1150instance (ToAuthInfo (BasicAuth realm usr), HasDocs api) => HasDocs (BasicAuth realm usr :> api) where
1003 docsFor Proxy (endpoint, action) =
1151 docsFor Proxy (endpoint, action) =
1004 docsFor (Proxy :: Proxy api) (endpoint, action')
1152 docsFor (Proxy :: Proxy api) (endpoint, action')
@@ -1006,6 +1154,9 @@ instance (ToAuthInfo (BasicAuth realm usr), HasDocs api) => HasDocs (BasicAuth r
1006 authProxy = Proxy :: Proxy (BasicAuth realm usr)
1154 authProxy = Proxy :: Proxy (BasicAuth realm usr)
1007 action' = over authInfo (|> toAuthInfo authProxy) action
1155 action' = over authInfo (|> toAuthInfo authProxy) action
1157+instance HasDocs (ToServantApi api) => HasDocs (NamedRoutes api) where
1158+ docsFor Proxy = docsFor (Proxy :: Proxy (ToServantApi api))
1009-- ToSample instances for simple types
1160-- ToSample instances for simple types
1010instance ToSample NoContent
1161instance ToSample NoContent
1011instance ToSample Bool
1162instance ToSample Bool
@@ -1022,6 +1173,7 @@ instance (ToSample a, ToSample b, ToSample c, ToSample d, ToSample e, ToSample f
1022instance ToSample a => ToSample (Maybe a)
1173instance ToSample a => ToSample (Maybe a)
1023instance (ToSample a, ToSample b) => ToSample (Either a b)
1174instance (ToSample a, ToSample b) => ToSample (Either a b)
1024instance ToSample a => ToSample [a]
1175instance ToSample a => ToSample [a]
1176+instance ToSample a => ToSample (NonEmpty a)
1026-- ToSample instances for Control.Applicative types
1178-- ToSample instances for Control.Applicative types
1027instance ToSample a => ToSample (Const a b)
1179instance ToSample a => ToSample (Const a b)
@@ -1035,3 +1187,6 @@ instance ToSample a => ToSample (Product a)
1035instance ToSample a => ToSample (First a)
1187instance ToSample a => ToSample (First a)
1036instance ToSample a => ToSample (Last a)
1188instance ToSample a => ToSample (Last a)
1037instance ToSample a => ToSample (Dual a)
1189instance ToSample a => ToSample (Dual a)
1192+-- >>> :set -XOverloadedStrings