servant-docs 0.11.9 → 0.13.1
raw patch · 7 files changed
Files
- CHANGELOG.md +23/−0
- example/greet.hs +5/−6
- golden/comprehensive.md +46/−28
- servant-docs.cabal +14/−14
- src/Servant/Docs/Internal.hs +70/−24
- src/Servant/Docs/Internal/Pretty.hs +19/−0
- test/Servant/DocsSpec.hs +0/−1
CHANGELOG.md view
@@ -1,6 +1,29 @@ [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.13.1+------++- Compatibility with newer dependencies and newer GHCs.++0.13+----++- WithResource combinator for Servant-managed resources. [#1630](https://github.com/haskell-servant/servant/pull/1630)++ For servant-docs, this meant the addition of `instance HasDocs api => HasDocs (WithResource res :> api)`++- Compatibility with GHC series 9.2, 9.4, 9.6++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
@@ -9,8 +9,7 @@ import Data.Aeson import Data.Proxy import Data.String.Conversions-import Data.Text- (Text)+import Data.Text (Text) import GHC.Generics import Servant.API import Servant.Docs@@ -75,7 +74,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 +92,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 +108,4 @@ docsGreet = docsWith defaultDocOptions [intro1, intro2] extra testApi main :: IO ()-main = putStrLn $ markdown docsGreet+main = putStrLn $ markdownWith (defRenderingOptions { _renderCurlBasePath = Just "http://localhost:80" }) docsGreet
golden/comprehensive.md view
@@ -12,7 +12,7 @@ - Example (`application/json;charset=utf-8`, `application/json`): -```javascript+```json ``` @@ -30,7 +30,7 @@ - Example (`application/json;charset=utf-8`, `application/json`): -```javascript+```json ``` @@ -48,7 +48,7 @@ - Example (`application/json;charset=utf-8`, `application/json`): -```javascript+```json ``` @@ -70,7 +70,7 @@ - Example (`application/json;charset=utf-8`, `application/json`): -```javascript+```json ``` @@ -92,7 +92,7 @@ - Example (`application/json;charset=utf-8`, `application/json`): -```javascript+```json ``` @@ -114,7 +114,7 @@ - Example (`application/json;charset=utf-8`, `application/json`): -```javascript+```json ``` @@ -135,7 +135,7 @@ - Example (`application/json;charset=utf-8`, `application/json`): -```javascript+```json ``` @@ -160,7 +160,7 @@ - Example (`application/json;charset=utf-8`, `application/json`): -```javascript+```json ``` @@ -178,7 +178,7 @@ - Example (`application/json;charset=utf-8`, `application/json`): -```javascript+```json ``` @@ -200,7 +200,7 @@ - Example (`application/json;charset=utf-8`, `application/json`): -```javascript+```json ``` @@ -218,7 +218,7 @@ - Example (`application/json;charset=utf-8`, `application/json`): -```javascript+```json 17 ``` @@ -240,7 +240,7 @@ - Example (`application/json;charset=utf-8`, `application/json`): -```javascript+```json ``` @@ -262,7 +262,7 @@ - Example (`application/json;charset=utf-8`, `application/json`): -```javascript+```json ``` @@ -280,7 +280,7 @@ - Example (`application/json;charset=utf-8`, `application/json`): -```javascript+```json ``` @@ -298,7 +298,7 @@ - Example (`application/json;charset=utf-8`, `application/json`): -```javascript+```json ``` @@ -316,7 +316,7 @@ - Example (`application/json;charset=utf-8`, `application/json`): -```javascript+```json ``` @@ -341,7 +341,7 @@ - Example (`application/json;charset=utf-8`, `application/json`): -```javascript+```json ``` @@ -366,7 +366,7 @@ - Example (`application/json;charset=utf-8`, `application/json`): -```javascript+```json ``` @@ -392,7 +392,7 @@ - Example (`application/json;charset=utf-8`, `application/json`): -```javascript+```json ``` @@ -410,7 +410,7 @@ - Example (`application/json;charset=utf-8`, `application/json`): -```javascript+```json 17 ``` @@ -446,7 +446,7 @@ - Example (`application/json;charset=utf-8`, `application/json`): -```javascript+```json ``` @@ -461,7 +461,7 @@ - Example (`application/json;charset=utf-8`, `application/json`): -```javascript+```json 17 ``` @@ -477,7 +477,7 @@ - Example (`application/json;charset=utf-8`, `application/json`): -```javascript+```json ``` @@ -492,7 +492,7 @@ - Example (`application/json;charset=utf-8`, `application/json`): -```javascript+```json 17 ``` @@ -508,7 +508,7 @@ - Example (`application/json;charset=utf-8`, `application/json`): -```javascript+```json ``` @@ -526,10 +526,28 @@ - Example (`application/json;charset=utf-8`, `application/json`): -```javascript+```json ``` +## GET /resource++### Response:++- Status code 200+- Headers: []++- Supported content types are:++ - `application/json;charset=utf-8`+ - `application/json`++- Example (`application/json;charset=utf-8`, `application/json`):++```json++```+ ## GET /streaming ### Request:@@ -568,7 +586,7 @@ - Example (`application/json;charset=utf-8`, `application/json`): -```javascript+```json ``` @@ -586,7 +604,7 @@ - Example (`application/json;charset=utf-8`, `application/json`): -```javascript+```json ```
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.13.1 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.10.7, GHC ==9.0.2, GHC ==9.2.8, GHC ==9.4.8, GHC ==9.6.4, GHC ==9.8.2, GHC ==9.10.1 extra-source-files: CHANGELOG.md@@ -41,25 +41,25 @@ -- -- note: mtl lower bound is so low because of GHC-7.8 build-depends:- base >= 4.9 && < 4.16- , bytestring >= 0.10.8.1 && < 0.12- , text >= 1.2.3.0 && < 1.3+ base >= 4.14 && < 4.21+ , bytestring >= 0.10.8.1 && < 0.13+ , text >= 1.2.3.0 && < 2.2 -- Servant dependencies build-depends:- servant >= 0.18 && <0.19+ servant >= 0.20.2 && <0.21 -- 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+ , base-compat >= 0.10.5 && < 0.15 , case-insensitive >= 1.2.0.11 && < 1.3- , hashable >= 1.2.7.0 && < 1.4+ , hashable >= 1.2.7.0 && < 1.6 , http-media >= 0.7.1.3 && < 0.9 , http-types >= 0.12.2 && < 0.13- , lens >= 4.17 && < 5.1+ , lens >= 4.17 && < 5.4 , string-conversions >= 0.4.0.1 && < 0.5 , universe-base >= 1.1.1 && < 1.2 , unordered-containers >= 0.2.9.0 && < 0.3@@ -102,7 +102,7 @@ -- Additional dependencies build-depends:- tasty >= 1.1.0.4 && < 1.3,+ tasty >= 1.1.0.4 && < 1.6, tasty-golden >= 2.3.2 && < 2.4, tasty-hunit >= 0.10.0.1 && < 0.11,- transformers >= 0.5.2.0 && < 0.6+ transformers >= 0.5.2.0 && < 0.7
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,17 +50,20 @@ (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+import Servant.API.TypeErrors import Servant.API.TypeLevel+import Servant.API.Generic import qualified Data.Universe.Helpers as U @@ -295,7 +296,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 +357,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 +376,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 +416,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"] -- > ]@@ -444,7 +448,7 @@ & apiEndpoints %~ HM.unionWith (flip combineAction) endpoints --- | Generate the docs for a given API that implements 'HasDocs' with with any+-- | Generate the docs for a given API that implements 'HasDocs' with any -- number of introduction(s) docsWithIntros :: HasDocs api => [DocIntro] -> Proxy api -> API docsWithIntros intros = docsWith defaultDocOptions intros mempty@@ -507,7 +511,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 +647,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 +658,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@@ -809,12 +814,11 @@ ("text", "html") -> "html" ("application", "xml") -> "xml" ("text", "xml") -> "xml"- ("application", "json") -> "javascript"+ ("application", "json") -> "json" ("application", "javascript") -> "javascript" ("text", "css") -> "css" (_, _) -> "" - contentStr mime_type body = "" : "```" <> markdownForType mime_type :@@ -839,6 +843,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 +1011,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@@ -1108,12 +1145,21 @@ instance HasDocs api => HasDocs (WithNamedContext name context api) where docsFor Proxy = docsFor (Proxy :: Proxy api) +instance HasDocs api => HasDocs (WithResource res :> api) where+ docsFor Proxy = docsFor (Proxy :: Proxy api)+ instance (ToAuthInfo (BasicAuth realm usr), HasDocs api) => HasDocs (BasicAuth realm usr :> api) where docsFor Proxy (endpoint, action) = docsFor (Proxy :: Proxy api) (endpoint, action') where authProxy = Proxy :: Proxy (BasicAuth realm usr) action' = over authInfo (|> toAuthInfo authProxy) action++instance+ ( HasDocs (ToServantApi api)+ , ErrorIfNoGeneric api+ ) => HasDocs (NamedRoutes api) where+ docsFor Proxy = docsFor (Proxy :: Proxy (ToServantApi api)) -- ToSample instances for simple types instance ToSample NoContent
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"