servant-docs 0.11.9 → 0.12
raw patch · 6 files changed
+96/−34 lines, 6 filesdep ~aesondep ~basedep ~base-compatnew-uploader
Dependency ranges changed: aeson, base, base-compat, hashable, lens, servant, tasty, text, transformers
Files
- CHANGELOG.md +9/−0
- example/greet.hs +4/−4
- servant-docs.cabal +7/−7
- src/Servant/Docs/Internal.hs +57/−22
- src/Servant/Docs/Internal/Pretty.hs +19/−0
- test/Servant/DocsSpec.hs +0/−1
CHANGELOG.md view
@@ -1,6 +1,15 @@ [The latest version of this document is on GitHub.](https://github.com/haskell-servant/servant/blob/master/servant-docs/CHANGELOG.md) [Changelog for `servant` package contains significant entries for all core packages.](https://github.com/haskell-servant/servant/blob/master/servant/CHANGELOG.md) +0.12+----++### Significant changes++- Generate sample cURL requests+ ([#1401](https://github.com/haskell-servant/servant/pull/1401/files)).+ Breaking change: requires sample header values to be supplied with `headers`.+ 0.11.9 ------
example/greet.hs view
@@ -75,7 +75,7 @@ -- API specification type TestApi = -- GET /hello/:name?capital={true, false} returns a Greet as JSON or PlainText- "hello" :> Capture "name" Text :> QueryParam "capital" Bool :> Get '[JSON, PlainText] Greet+ "hello" :> Capture "name" Text :> Header "X-Num-Fairies" Int :> QueryParam "capital" Bool :> Get '[JSON, PlainText] Greet -- POST /greet with a Greet as JSON in the request body, -- returns a Greet as JSON@@ -93,9 +93,9 @@ extra :: ExtraInfo TestApi extra = extraInfo (Proxy :: Proxy ("greet" :> Capture "greetid" Text :> Delete '[JSON] NoContent)) $- defAction & headers <>~ ["unicorns"]+ defAction & headers <>~ [("X-Num-Unicorns", "1")] & notes <>~ [ DocNote "Title" ["This is some text"]- , DocNote "Second secton" ["And some more"]+ , DocNote "Second section" ["And some more"] ] -- Generate the data that lets us have API docs. This@@ -109,4 +109,4 @@ docsGreet = docsWith defaultDocOptions [intro1, intro2] extra testApi main :: IO ()-main = putStrLn $ markdown docsGreet+main = putStrLn $ markdownWith (defRenderingOptions { _renderCurlBasePath = Just "http://localhost:80" }) docsGreet
servant-docs.cabal view
@@ -1,6 +1,6 @@-cabal-version: >=1.10+cabal-version: 2.2 name: servant-docs-version: 0.11.9+version: 0.12 synopsis: generate API docs for your servant webservice category: Servant, Web@@ -13,13 +13,13 @@ homepage: http://docs.servant.dev/ bug-reports: http://github.com/haskell-servant/servant/issues-license: BSD3+license: BSD-3-Clause license-file: LICENSE author: Servant Contributors maintainer: haskell-servant-maintainers@googlegroups.com copyright: 2014-2016 Zalora South East Asia Pte Ltd, 2016-2019 Servant Contributors build-type: Simple-tested-with: GHC ==8.0.2 || ==8.2.2 || ==8.4.4 || ==8.6.5 || ==8.8.4 || ==8.10.2 || ==9.0.1+tested-with: GHC ==8.6.5 || ==8.8.4 || ==8.10.2 || ==9.0.1 extra-source-files: CHANGELOG.md@@ -47,12 +47,12 @@ -- Servant dependencies build-depends:- servant >= 0.18 && <0.19+ servant >= 0.18 && <0.20 -- Other dependencies: Lower bound around what is in the latest Stackage LTS. -- Here can be exceptions if we really need features from the newer versions. build-depends:- aeson >= 1.4.1.0 && < 1.6+ aeson >= 1.4.1.0 && < 3 , aeson-pretty >= 0.8.5 && < 0.9 , base-compat >= 0.10.5 && < 0.12 , case-insensitive >= 1.2.0.11 && < 1.3@@ -102,7 +102,7 @@ -- Additional dependencies build-depends:- tasty >= 1.1.0.4 && < 1.3,+ tasty >= 1.1.0.4 && < 1.5, tasty-golden >= 2.3.2 && < 2.4, tasty-hunit >= 0.10.0.1 && < 0.11, transformers >= 0.5.2.0 && < 0.6
src/Servant/Docs/Internal.hs view
@@ -25,16 +25,14 @@ import Control.Arrow (second) import Control.Lens- (makeLenses, mapped, over, set, traversed, view, (%~), (&),- (.~), (<>~), (^.), (|>))+ (makeLenses, mapped, each, over, set, to, toListOf, traversed, view,+ _1, (%~), (&), (.~), (<>~), (^.), (|>)) import qualified Data.ByteString.Char8 as BSC import Data.ByteString.Lazy.Char8 (ByteString) import qualified Data.CaseInsensitive as CI import Data.Foldable- (toList)-import Data.Foldable- (fold)+ (fold, toList) import Data.Hashable (Hashable) import Data.HashMap.Strict@@ -52,13 +50,14 @@ (comparing) import Data.Proxy (Proxy (Proxy))-import Data.Semigroup- (Semigroup (..)) import Data.String.Conversions (cs) import Data.Text (Text, unpack) import GHC.Generics+ (Generic, Rep, K1(K1), M1(M1), U1(U1), V1,+ (:*:)((:*:)), (:+:)(L1, R1))+import qualified GHC.Generics as G import GHC.TypeLits import Servant.API import Servant.API.ContentTypes@@ -295,7 +294,7 @@ data Action = Action { _authInfo :: [DocAuthentication] -- user supplied info , _captures :: [DocCapture] -- type collected + user supplied info- , _headers :: [Text] -- type collected+ , _headers :: [HTTP.Header] -- type collected , _params :: [DocQueryParam] -- type collected + user supplied info , _fragment :: Maybe DocFragment -- type collected + user supplied info , _notes :: [DocNote] -- user supplied@@ -356,12 +355,14 @@ -- -- @since 0.11.1 data RenderingOptions = RenderingOptions- { _requestExamples :: !ShowContentTypes+ { _requestExamples :: !ShowContentTypes -- ^ How many content types to display for request body examples?- , _responseExamples :: !ShowContentTypes+ , _responseExamples :: !ShowContentTypes -- ^ How many content types to display for response body examples?- , _notesHeading :: !(Maybe String)+ , _notesHeading :: !(Maybe String) -- ^ Optionally group all 'notes' together under a common heading.+ , _renderCurlBasePath :: !(Maybe String)+ -- ^ Optionally render example curl requests under a common base path (e.g. `http://localhost:80`). } deriving (Show) -- | Default API generation options.@@ -373,9 +374,10 @@ -- @since 0.11.1 defRenderingOptions :: RenderingOptions defRenderingOptions = RenderingOptions- { _requestExamples = AllContentTypes- , _responseExamples = AllContentTypes- , _notesHeading = Nothing+ { _requestExamples = AllContentTypes+ , _responseExamples = AllContentTypes+ , _notesHeading = Nothing+ , _renderCurlBasePath = Nothing } -- gimme some lenses@@ -412,7 +414,7 @@ -- > extra :: ExtraInfo TestApi -- > extra = -- > extraInfo (Proxy :: Proxy ("greet" :> Capture "greetid" Text :> Delete)) $--- > defAction & headers <>~ ["unicorns"]+-- > defAction & headers <>~ [("X-Num-Unicorns", 1)] -- > & notes <>~ [ DocNote "Title" ["This is some text"] -- > , DocNote "Second section" ["And some more"] -- > ]@@ -507,7 +509,7 @@ -- | Default sample Generic-based inputs/outputs. defaultSamples :: forall a. (Generic a, GToSample (Rep a)) => Proxy a -> [(Text, a)]-defaultSamples _ = second to <$> gtoSamples (Proxy :: Proxy (Rep a))+defaultSamples _ = second G.to <$> gtoSamples (Proxy :: Proxy (Rep a)) -- | @'ToSample'@ for Generics. --@@ -643,7 +645,7 @@ -- -- @since 0.11.1 markdownWith :: RenderingOptions -> API -> String-markdownWith RenderingOptions{..} api = unlines $+markdownWith RenderingOptions{..} api = unlines $ introsStr (api ^. apiIntros) ++ (concatMap (uncurry printEndpoint) . sort . HM.toList $ api ^. apiEndpoints) @@ -654,11 +656,12 @@ notesStr (action ^. notes) ++ authStr (action ^. authInfo) ++ capturesStr (action ^. captures) ++- headersStr (action ^. headers) +++ headersStr (toListOf (headers . each . _1 . to (T.pack . BSC.unpack . CI.original)) action) ++ paramsStr meth (action ^. params) ++ fragmentStr (action ^. fragment) ++ rqbodyStr (action ^. rqtypes) (action ^. rqbody) ++ responseStr (action ^. response) +++ maybe [] (curlStr endpoint (action ^. headers) (action ^. rqbody)) _renderCurlBasePath ++ [] where str = "## " ++ BSC.unpack meth@@ -814,7 +817,6 @@ ("text", "css") -> "css" (_, _) -> "" - contentStr mime_type body = "" : "```" <> markdownForType mime_type :@@ -839,6 +841,36 @@ xs -> formatBodies _responseExamples xs + curlStr :: Endpoint -> [HTTP.Header] -> [(Text, M.MediaType, ByteString)] -> String -> [String]+ curlStr endpoint hdrs reqBodies basePath =+ [ "### Sample Request:"+ , ""+ , "```bash"+ , "curl -X" ++ BSC.unpack (endpoint ^. method) ++ " \\"+ ] <>+ maybe [] pure mbMediaTypeStr <>+ headersStrs <>+ maybe [] pure mbReqBodyStr <>+ [ " " ++ basePath ++ showPath (endpoint ^. path)+ , "```"+ , ""+ ]++ where escapeQuotes :: String -> String+ escapeQuotes = concatMap $ \c -> case c of+ '\"' -> "\\\""+ _ -> [c]+ mbReqBody = listToMaybe reqBodies+ mbMediaTypeStr = mkMediaTypeStr <$> mbReqBody+ headersStrs = mkHeaderStr <$> hdrs+ mbReqBodyStr = mkReqBodyStr <$> mbReqBody+ mkMediaTypeStr (_, media_type, _) =+ " -H \"Content-Type: " ++ show media_type ++ "\" \\"+ mkHeaderStr (hdrName, hdrVal) =+ " -H \"" ++ escapeQuotes (cs (CI.original hdrName)) ++ ": " +++ escapeQuotes (cs hdrVal) ++ "\" \\"+ mkReqBodyStr (_, _, body) = " -d \"" ++ escapeQuotes (cs body) ++ "\" \\"+ -- * Instances -- | The generated docs for @a ':<|>' b@ just appends the docs@@ -977,14 +1009,17 @@ status = fromInteger $ natVal (Proxy :: Proxy status) p = Proxy :: Proxy a -instance (KnownSymbol sym, HasDocs api)+instance (ToHttpApiData a, ToSample a, KnownSymbol sym, HasDocs api) => HasDocs (Header' mods sym a :> api) where docsFor Proxy (endpoint, action) = docsFor subApiP (endpoint, action') where subApiP = Proxy :: Proxy api- action' = over headers (|> headername) action- headername = T.pack $ symbolVal (Proxy :: Proxy sym)+ action' = over headers (|> (headerName, headerVal)) action+ headerName = CI.mk . cs $ symbolVal (Proxy :: Proxy sym)+ headerVal = case toSample (Proxy :: Proxy a) of+ Just x -> cs $ toHeader x+ Nothing -> "<no header sample provided>" instance (KnownSymbol sym, ToParam (QueryParam' mods sym a), HasDocs api) => HasDocs (QueryParam' mods sym a :> api) where
src/Servant/Docs/Internal/Pretty.hs view
@@ -18,6 +18,7 @@ import Network.HTTP.Media ((//)) import Servant.API+import Servant.API.Verbs -- | PrettyJSON content type. data PrettyJSON@@ -46,6 +47,24 @@ Pretty (Put cs r) = Put (Pretty cs) r Pretty (Delete cs r) = Delete (Pretty cs) r Pretty (Patch cs r) = Patch (Pretty cs) r+ Pretty (GetPartialContent cs r) = GetPartialContent (Pretty cs) r+ Pretty (PutResetContent cs r) = PutResetContent (Pretty cs) r+ Pretty (PatchResetContent cs r) = PatchResetContent (Pretty cs) r+ Pretty (DeleteResetContent cs r) = DeleteResetContent (Pretty cs) r+ Pretty (PostResetContent cs r) = PostResetContent (Pretty cs) r+ Pretty (GetResetContent cs r) = GetResetContent (Pretty cs) r+ Pretty (PutNonAuthoritative cs r) = PutNonAuthoritative (Pretty cs) r+ Pretty (PatchNonAuthoritative cs r) = PatchNonAuthoritative (Pretty cs) r+ Pretty (DeleteNonAuthoritative cs r) = DeleteNonAuthoritative (Pretty cs) r+ Pretty (PostNonAuthoritative cs r) = PostNonAuthoritative (Pretty cs) r+ Pretty (GetNonAuthoritative cs r) = GetNonAuthoritative (Pretty cs) r+ Pretty (PutAccepted cs r) = PutAccepted (Pretty cs) r+ Pretty (PatchAccepted cs r) = PatchAccepted (Pretty cs) r+ Pretty (DeleteAccepted cs r) = DeleteAccepted (Pretty cs) r+ Pretty (PostAccepted cs r) = PostAccepted (Pretty cs) r+ Pretty (GetAccepted cs r) = GetAccepted (Pretty cs) r+ Pretty (PutCreated cs r) = PutCreated (Pretty cs) r+ Pretty (PostCreated cs r) = PostCreated (Pretty cs) r Pretty (ReqBody cs r) = ReqBody (Pretty cs) r Pretty (JSON ': xs) = PrettyJSON ': xs Pretty (x ': xs) = x ': Pretty xs
test/Servant/DocsSpec.hs view
@@ -130,7 +130,6 @@ md `shouldContain` "\"dt1field1\":\"field 1\"" it "contains response samples - dt1field2" $ md `shouldContain` "\"dt1field2\":13"- it "contains request body samples" $ md `shouldContain` "17"