servant-docs-simple 0.2.0.1 → 0.3.0.0
raw patch · 6 files changed
+150/−86 lines, 6 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
- Servant.Docs.Simple.Parse: class HasDocumentApi api
- Servant.Docs.Simple.Parse: class HasParsable api
- Servant.Docs.Simple.Parse: document :: HasDocumentApi api => Route -> [(Parameter, Details)] -> (Route, OMap Parameter Details)
- Servant.Docs.Simple.Parse: instance (Servant.Docs.Simple.Parse.HasDocumentApi b, Data.Typeable.Internal.Typeable ct, Data.Typeable.Internal.Typeable typ) => Servant.Docs.Simple.Parse.HasDocumentApi (Servant.API.ReqBody.ReqBody' m ct typ Servant.API.Sub.:> b)
- Servant.Docs.Simple.Parse: instance (Servant.Docs.Simple.Parse.HasDocumentApi b, Data.Typeable.Internal.Typeable ct, Data.Typeable.Internal.Typeable typ) => Servant.Docs.Simple.Parse.HasDocumentApi (Servant.API.Stream.StreamBody' m ct typ Servant.API.Sub.:> b)
- Servant.Docs.Simple.Parse: instance (Servant.Docs.Simple.Parse.HasDocumentApi b, GHC.TypeLits.KnownSymbol dRoute, Data.Typeable.Internal.Typeable t) => Servant.Docs.Simple.Parse.HasDocumentApi (Servant.API.Capture.Capture' m dRoute t Servant.API.Sub.:> b)
- Servant.Docs.Simple.Parse: instance (Servant.Docs.Simple.Parse.HasDocumentApi b, GHC.TypeLits.KnownSymbol dRoute, Data.Typeable.Internal.Typeable t) => Servant.Docs.Simple.Parse.HasDocumentApi (Servant.API.Capture.CaptureAll dRoute t Servant.API.Sub.:> b)
- Servant.Docs.Simple.Parse: instance (Servant.Docs.Simple.Parse.HasDocumentApi b, GHC.TypeLits.KnownSymbol desc) => Servant.Docs.Simple.Parse.HasDocumentApi (Servant.API.Description.Description desc Servant.API.Sub.:> b)
- Servant.Docs.Simple.Parse: instance (Servant.Docs.Simple.Parse.HasDocumentApi b, GHC.TypeLits.KnownSymbol param) => Servant.Docs.Simple.Parse.HasDocumentApi (Servant.API.QueryParam.QueryFlag param Servant.API.Sub.:> b)
- Servant.Docs.Simple.Parse: instance (Servant.Docs.Simple.Parse.HasDocumentApi b, GHC.TypeLits.KnownSymbol param, Data.Typeable.Internal.Typeable typ) => Servant.Docs.Simple.Parse.HasDocumentApi (Servant.API.QueryParam.QueryParam' m param typ Servant.API.Sub.:> b)
- Servant.Docs.Simple.Parse: instance (Servant.Docs.Simple.Parse.HasDocumentApi b, GHC.TypeLits.KnownSymbol param, Data.Typeable.Internal.Typeable typ) => Servant.Docs.Simple.Parse.HasDocumentApi (Servant.API.QueryParam.QueryParams param typ Servant.API.Sub.:> b)
- Servant.Docs.Simple.Parse: instance (Servant.Docs.Simple.Parse.HasDocumentApi b, GHC.TypeLits.KnownSymbol realm, Data.Typeable.Internal.Typeable a) => Servant.Docs.Simple.Parse.HasDocumentApi (Servant.API.BasicAuth.BasicAuth realm a Servant.API.Sub.:> b)
- Servant.Docs.Simple.Parse: instance (Servant.Docs.Simple.Parse.HasDocumentApi b, GHC.TypeLits.KnownSymbol route) => Servant.Docs.Simple.Parse.HasDocumentApi (route Servant.API.Sub.:> b)
- Servant.Docs.Simple.Parse: instance (Servant.Docs.Simple.Parse.HasDocumentApi b, GHC.TypeLits.KnownSymbol s) => Servant.Docs.Simple.Parse.HasDocumentApi (Servant.API.Description.Summary s Servant.API.Sub.:> b)
- Servant.Docs.Simple.Parse: instance (Servant.Docs.Simple.Parse.HasDocumentApi b, GHC.TypeLits.KnownSymbol token) => Servant.Docs.Simple.Parse.HasDocumentApi (Servant.API.Experimental.Auth.AuthProtect token Servant.API.Sub.:> b)
- Servant.Docs.Simple.Parse: instance Servant.Docs.Simple.Parse.HasCollatable (Servant.API.TypeLevel.Endpoints a) => Servant.Docs.Simple.Parse.HasParsable a
- Servant.Docs.Simple.Parse: instance Servant.Docs.Simple.Parse.HasDocumentApi b => Servant.Docs.Simple.Parse.HasDocumentApi (Data.Vault.Lazy.Vault Servant.API.Sub.:> b)
- Servant.Docs.Simple.Parse: instance Servant.Docs.Simple.Parse.HasDocumentApi b => Servant.Docs.Simple.Parse.HasDocumentApi (Network.HTTP.Types.Version.HttpVersion Servant.API.Sub.:> b)
- Servant.Docs.Simple.Parse: instance Servant.Docs.Simple.Parse.HasDocumentApi b => Servant.Docs.Simple.Parse.HasDocumentApi (Servant.API.IsSecure.IsSecure Servant.API.Sub.:> b)
- Servant.Docs.Simple.Parse: instance Servant.Docs.Simple.Parse.HasDocumentApi b => Servant.Docs.Simple.Parse.HasDocumentApi (Servant.API.RemoteHost.RemoteHost Servant.API.Sub.:> b)
- Servant.Docs.Simple.Parse: instance Servant.Docs.Simple.Parse.HasParsable Servant.API.Empty.EmptyAPI
- Servant.Docs.Simple.Parse: instance forall a (api :: a) (b :: [a]). (Servant.Docs.Simple.Parse.HasDocumentApi api, Servant.Docs.Simple.Parse.HasCollatable b) => Servant.Docs.Simple.Parse.HasCollatable (api : b)
- Servant.Docs.Simple.Parse: instance forall k b (ct :: GHC.Types.Symbol) (typ :: k) (m :: [*]). (Servant.Docs.Simple.Parse.HasDocumentApi b, GHC.TypeLits.KnownSymbol ct, Data.Typeable.Internal.Typeable typ) => Servant.Docs.Simple.Parse.HasDocumentApi (Servant.API.Header.Header' m ct typ Servant.API.Sub.:> b)
- Servant.Docs.Simple.Parse: instance forall k1 (m :: k1) (ct :: [*]) typ (s :: GHC.Types.Nat). (Data.Typeable.Internal.Typeable m, Data.Typeable.Internal.Typeable ct, Data.Typeable.Internal.Typeable typ) => Servant.Docs.Simple.Parse.HasDocumentApi (Servant.API.Verbs.Verb m s ct typ)
- Servant.Docs.Simple.Parse: parse :: HasParsable api => ApiDocs
- Servant.Docs.Simple.Render: instance Servant.Docs.Simple.Render.Renderable (Servant.Docs.Simple.Render.Pretty ann)
+ Servant.Docs.Simple: stdoutMarkdown :: forall api. HasParsableApi api => IO ()
+ Servant.Docs.Simple: writeDocsMarkdown :: forall api. HasParsableApi api => FilePath -> IO ()
+ Servant.Docs.Simple.Parse: class HasParsableApi api
+ Servant.Docs.Simple.Parse: class HasParsableEndpoint e
+ Servant.Docs.Simple.Parse: instance (Servant.Docs.Simple.Parse.HasParsableEndpoint b, Data.Typeable.Internal.Typeable ct, Data.Typeable.Internal.Typeable typ) => Servant.Docs.Simple.Parse.HasParsableEndpoint (Servant.API.ReqBody.ReqBody' m ct typ Servant.API.Sub.:> b)
+ Servant.Docs.Simple.Parse: instance (Servant.Docs.Simple.Parse.HasParsableEndpoint b, Data.Typeable.Internal.Typeable ct, Data.Typeable.Internal.Typeable typ) => Servant.Docs.Simple.Parse.HasParsableEndpoint (Servant.API.Stream.StreamBody' m ct typ Servant.API.Sub.:> b)
+ Servant.Docs.Simple.Parse: instance (Servant.Docs.Simple.Parse.HasParsableEndpoint b, GHC.TypeLits.KnownSymbol dRoute, Data.Typeable.Internal.Typeable t) => Servant.Docs.Simple.Parse.HasParsableEndpoint (Servant.API.Capture.Capture' m dRoute t Servant.API.Sub.:> b)
+ Servant.Docs.Simple.Parse: instance (Servant.Docs.Simple.Parse.HasParsableEndpoint b, GHC.TypeLits.KnownSymbol dRoute, Data.Typeable.Internal.Typeable t) => Servant.Docs.Simple.Parse.HasParsableEndpoint (Servant.API.Capture.CaptureAll dRoute t Servant.API.Sub.:> b)
+ Servant.Docs.Simple.Parse: instance (Servant.Docs.Simple.Parse.HasParsableEndpoint b, GHC.TypeLits.KnownSymbol desc) => Servant.Docs.Simple.Parse.HasParsableEndpoint (Servant.API.Description.Description desc Servant.API.Sub.:> b)
+ Servant.Docs.Simple.Parse: instance (Servant.Docs.Simple.Parse.HasParsableEndpoint b, GHC.TypeLits.KnownSymbol param) => Servant.Docs.Simple.Parse.HasParsableEndpoint (Servant.API.QueryParam.QueryFlag param Servant.API.Sub.:> b)
+ Servant.Docs.Simple.Parse: instance (Servant.Docs.Simple.Parse.HasParsableEndpoint b, GHC.TypeLits.KnownSymbol param, Data.Typeable.Internal.Typeable typ) => Servant.Docs.Simple.Parse.HasParsableEndpoint (Servant.API.QueryParam.QueryParam' m param typ Servant.API.Sub.:> b)
+ Servant.Docs.Simple.Parse: instance (Servant.Docs.Simple.Parse.HasParsableEndpoint b, GHC.TypeLits.KnownSymbol param, Data.Typeable.Internal.Typeable typ) => Servant.Docs.Simple.Parse.HasParsableEndpoint (Servant.API.QueryParam.QueryParams param typ Servant.API.Sub.:> b)
+ Servant.Docs.Simple.Parse: instance (Servant.Docs.Simple.Parse.HasParsableEndpoint b, GHC.TypeLits.KnownSymbol realm, Data.Typeable.Internal.Typeable a) => Servant.Docs.Simple.Parse.HasParsableEndpoint (Servant.API.BasicAuth.BasicAuth realm a Servant.API.Sub.:> b)
+ Servant.Docs.Simple.Parse: instance (Servant.Docs.Simple.Parse.HasParsableEndpoint b, GHC.TypeLits.KnownSymbol route) => Servant.Docs.Simple.Parse.HasParsableEndpoint (route Servant.API.Sub.:> b)
+ Servant.Docs.Simple.Parse: instance (Servant.Docs.Simple.Parse.HasParsableEndpoint b, GHC.TypeLits.KnownSymbol s) => Servant.Docs.Simple.Parse.HasParsableEndpoint (Servant.API.Description.Summary s Servant.API.Sub.:> b)
+ Servant.Docs.Simple.Parse: instance (Servant.Docs.Simple.Parse.HasParsableEndpoint b, GHC.TypeLits.KnownSymbol token) => Servant.Docs.Simple.Parse.HasParsableEndpoint (Servant.API.Experimental.Auth.AuthProtect token Servant.API.Sub.:> b)
+ Servant.Docs.Simple.Parse: instance Servant.Docs.Simple.Parse.HasCollatable (Servant.API.TypeLevel.Endpoints a) => Servant.Docs.Simple.Parse.HasParsableApi a
+ Servant.Docs.Simple.Parse: instance Servant.Docs.Simple.Parse.HasParsableApi Servant.API.Empty.EmptyAPI
+ Servant.Docs.Simple.Parse: instance Servant.Docs.Simple.Parse.HasParsableEndpoint b => Servant.Docs.Simple.Parse.HasParsableEndpoint (Data.Vault.Lazy.Vault Servant.API.Sub.:> b)
+ Servant.Docs.Simple.Parse: instance Servant.Docs.Simple.Parse.HasParsableEndpoint b => Servant.Docs.Simple.Parse.HasParsableEndpoint (Network.HTTP.Types.Version.HttpVersion Servant.API.Sub.:> b)
+ Servant.Docs.Simple.Parse: instance Servant.Docs.Simple.Parse.HasParsableEndpoint b => Servant.Docs.Simple.Parse.HasParsableEndpoint (Servant.API.IsSecure.IsSecure Servant.API.Sub.:> b)
+ Servant.Docs.Simple.Parse: instance Servant.Docs.Simple.Parse.HasParsableEndpoint b => Servant.Docs.Simple.Parse.HasParsableEndpoint (Servant.API.RemoteHost.RemoteHost Servant.API.Sub.:> b)
+ Servant.Docs.Simple.Parse: instance forall a (e :: a) (b :: [a]). (Servant.Docs.Simple.Parse.HasParsableEndpoint e, Servant.Docs.Simple.Parse.HasCollatable b) => Servant.Docs.Simple.Parse.HasCollatable (e : b)
+ Servant.Docs.Simple.Parse: instance forall k b (ct :: GHC.Types.Symbol) (typ :: k) (m :: [*]). (Servant.Docs.Simple.Parse.HasParsableEndpoint b, GHC.TypeLits.KnownSymbol ct, Data.Typeable.Internal.Typeable typ) => Servant.Docs.Simple.Parse.HasParsableEndpoint (Servant.API.Header.Header' m ct typ Servant.API.Sub.:> b)
+ Servant.Docs.Simple.Parse: instance forall k1 (m :: k1) (ct :: [*]) typ (s :: GHC.Types.Nat). (Data.Typeable.Internal.Typeable m, Data.Typeable.Internal.Typeable ct, Data.Typeable.Internal.Typeable typ) => Servant.Docs.Simple.Parse.HasParsableEndpoint (Servant.API.Verbs.Verb m s ct typ)
+ Servant.Docs.Simple.Parse: parseApi :: HasParsableApi api => ApiDocs
+ Servant.Docs.Simple.Parse: parseEndpoint :: HasParsableEndpoint e => Route -> [(Parameter, Details)] -> (Route, OMap Parameter Details)
+ Servant.Docs.Simple.Render: Markdown :: Text -> Markdown
+ Servant.Docs.Simple.Render: [getMarkdown] :: Markdown -> Text
+ Servant.Docs.Simple.Render: instance GHC.Classes.Eq Servant.Docs.Simple.Render.Markdown
+ Servant.Docs.Simple.Render: instance GHC.Show.Show Servant.Docs.Simple.Render.Markdown
+ Servant.Docs.Simple.Render: instance Servant.Docs.Simple.Render.Renderable Servant.Docs.Simple.Render.Markdown
+ Servant.Docs.Simple.Render: instance Servant.Docs.Simple.Render.Renderable Servant.Docs.Simple.Render.Pretty
+ Servant.Docs.Simple.Render: newtype Markdown
- Servant.Docs.Simple: document :: forall api. HasParsable api => PlainText
+ Servant.Docs.Simple: document :: forall api. HasParsableApi api => PlainText
- Servant.Docs.Simple: documentWith :: forall api a. (HasParsable api, Renderable a) => a
+ Servant.Docs.Simple: documentWith :: forall api a. (HasParsableApi api, Renderable a) => a
- Servant.Docs.Simple: stdoutJson :: forall api. HasParsable api => IO ()
+ Servant.Docs.Simple: stdoutJson :: forall api. HasParsableApi api => IO ()
- Servant.Docs.Simple: stdoutPlainText :: forall api. HasParsable api => IO ()
+ Servant.Docs.Simple: stdoutPlainText :: forall api. HasParsableApi api => IO ()
- Servant.Docs.Simple: writeDocsJson :: forall api. HasParsable api => FilePath -> IO ()
+ Servant.Docs.Simple: writeDocsJson :: forall api. HasParsableApi api => FilePath -> IO ()
- Servant.Docs.Simple: writeDocsPlainText :: forall api. HasParsable api => FilePath -> IO ()
+ Servant.Docs.Simple: writeDocsPlainText :: forall api. HasParsableApi api => FilePath -> IO ()
- Servant.Docs.Simple.Render: Pretty :: Doc ann -> Pretty ann
+ Servant.Docs.Simple.Render: Pretty :: Doc Ann -> Pretty
- Servant.Docs.Simple.Render: [getPretty] :: Pretty ann -> Doc ann
+ Servant.Docs.Simple.Render: [getPretty] :: Pretty -> Doc Ann
- Servant.Docs.Simple.Render: newtype Pretty ann
+ Servant.Docs.Simple.Render: newtype Pretty
Files
- CHANGELOG.md +12/−0
- servant-docs-simple.cabal +2/−1
- src/Servant/Docs/Simple.hs +20/−9
- src/Servant/Docs/Simple/Parse.hs +60/−60
- src/Servant/Docs/Simple/Render.hs +52/−12
- test/Test/Servant/Docs/Simple/Parse.hs +4/−4
CHANGELOG.md view
@@ -15,3 +15,15 @@ ## 0.2.0.1 * Update CHANGELOG, README++## 0.3.0.0++* Add markdown support++* Update typeclass names in `Servant.Docs.Simple.Parse`:++ `HasParsable` -> `HasParsableApi`+ + `HasDocumentApi` -> `HasParsableEndpoint`+ + `parse` -> `parseApi`
servant-docs-simple.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.4 name: servant-docs-simple-version: 0.2.0.1+version: 0.3.0.0 synopsis: Generate endpoints overview for Servant API description:@@ -63,6 +63,7 @@ DerivingStrategies FlexibleInstances FunctionalDependencies+ LambdaCase OverloadedStrings PolyKinds ScopedTypeVariables
src/Servant/Docs/Simple.hs view
@@ -70,8 +70,10 @@ module Servant.Docs.Simple ( document , documentWith , stdoutJson+ , stdoutMarkdown , stdoutPlainText , writeDocsJson+ , writeDocsMarkdown , writeDocsPlainText ) where @@ -79,30 +81,39 @@ import qualified Data.ByteString.Lazy as B (writeFile) import qualified Data.ByteString.Lazy.Char8 as BC (putStrLn) import qualified Data.Text.IO as T (putStrLn, writeFile)-import Servant.Docs.Simple.Parse (HasParsable (..))-import Servant.Docs.Simple.Render (Json (..), PlainText (..), Renderable (..)) +import Servant.Docs.Simple.Parse (HasParsableApi (..))+import Servant.Docs.Simple.Render (Json (..), Markdown (..), PlainText (..), Renderable (..)) + -- | Write documentation as PlainText to file-writeDocsPlainText :: forall api. HasParsable api => FilePath -> IO ()+writeDocsPlainText :: forall api. HasParsableApi api => FilePath -> IO () writeDocsPlainText fp = T.writeFile fp . getPlainText $ document @api +-- | Write documentation as Markdown to file+writeDocsMarkdown :: forall api. HasParsableApi api => FilePath -> IO ()+writeDocsMarkdown fp = T.writeFile fp . getMarkdown $ documentWith @api @Markdown+ -- | Write documentation as JSON to file-writeDocsJson :: forall api. HasParsable api => FilePath -> IO ()+writeDocsJson :: forall api. HasParsableApi api => FilePath -> IO () writeDocsJson fp = B.writeFile fp . encodePretty . getJson $ documentWith @api @Json -- | Write documentation as PlainText to stdout-stdoutPlainText :: forall api. HasParsable api => IO ()+stdoutPlainText :: forall api. HasParsableApi api => IO () stdoutPlainText = T.putStrLn . getPlainText $ document @api +-- | Write documentation as Markdown to stdout+stdoutMarkdown :: forall api. HasParsableApi api => IO ()+stdoutMarkdown = T.putStrLn . getMarkdown $ documentWith @api @Markdown+ -- | Write documentation as JSON to stdout-stdoutJson :: forall api. HasParsable api => IO ()+stdoutJson :: forall api. HasParsableApi api => IO () stdoutJson = BC.putStrLn . encodePretty . getJson $ documentWith @api @Json -- | Convert API type into PlainText format-document :: forall api. HasParsable api => PlainText+document :: forall api. HasParsableApi api => PlainText document = documentWith @api @PlainText -- | Convert API type into specified formats-documentWith :: forall api a. (HasParsable api, Renderable a) => a-documentWith = render @a (parse @api)+documentWith :: forall api a. (HasParsableApi api, Renderable a) => a+documentWith = render @a (parseApi @api)
src/Servant/Docs/Simple/Parse.hs view
@@ -46,8 +46,8 @@ {-# LANGUAGE UndecidableInstances #-} module Servant.Docs.Simple.Parse- ( HasDocumentApi (..)- , HasParsable (..)+ ( HasParsableEndpoint (..)+ , HasParsableApi (..) , symbolVal' , toDetails , typeText@@ -69,16 +69,16 @@ import Servant.Docs.Simple.Render (ApiDocs (..), Details (..), Parameter, Route) -- | Flattens API into type level list of Endpoints-class HasParsable api where- parse :: ApiDocs+class HasParsableApi api where+ parseApi :: ApiDocs -- | If the flattened API can be collated into documentation, it is parsable-instance HasCollatable (S.Endpoints a) => HasParsable a where- parse = collate @(S.Endpoints a)+instance HasCollatable (S.Endpoints a) => HasParsableApi a where+ parseApi = collate @(S.Endpoints a) -- | Empty APIs should have no documentation-instance {-# OVERLAPPING #-} HasParsable EmptyAPI where- parse = collate @'[]+instance {-# OVERLAPPING #-} HasParsableApi EmptyAPI where+ parseApi = collate @'[] -- | Folds api endpoints into documentation class HasCollatable api where@@ -86,8 +86,8 @@ collate :: ApiDocs -- | Collapse a type-level list of API endpoints into documentation-instance (HasDocumentApi api, HasCollatable b) => HasCollatable (api ': b) where- collate = ApiDocs $ (Details <$> documentEndpoint @api) |< previous+instance (HasParsableEndpoint e, HasCollatable b) => HasCollatable (e ': b) where+ collate = ApiDocs $ (Details <$> documentEndpoint @e) |< previous where ApiDocs previous = collate @b -- | Terminal step when there are no more endpoints left to recurse over@@ -95,64 +95,64 @@ collate = ApiDocs empty -- | Folds an api endpoint into documentation-documentEndpoint :: forall a. HasDocumentApi a => (Route, OMap Parameter Details)-documentEndpoint = document @a "" []+documentEndpoint :: forall a. HasParsableEndpoint a => (Route, OMap Parameter Details)+documentEndpoint = parseEndpoint @a "" [] -- | Folds an api endpoint into documentation-class HasDocumentApi api where+class HasParsableEndpoint e where -- | We use this to destructure the API type and convert it into documentation- document :: Route -- ^ Route documentation- -> [(Parameter, Details)] -- ^ Everything else documentation- -> (Route, OMap Parameter Details) -- ^ Generated documentation for the route+ parseEndpoint :: Route -- ^ Route documentation+ -> [(Parameter, Details)] -- ^ Everything else documentation+ -> (Route, OMap Parameter Details) -- ^ Generated documentation for the route -- | Static route documentation-instance (HasDocumentApi b, KnownSymbol route) => HasDocumentApi ((route :: Symbol) :> b) where- document r = document @b formatted- where formatted = fold [r, "/", fragment]- fragment = symbolVal' @route+instance (HasParsableEndpoint b, KnownSymbol route) => HasParsableEndpoint ((route :: Symbol) :> b) where+ parseEndpoint r = parseEndpoint @b formatted+ where formatted = fold [r, "/", fragment]+ fragment = symbolVal' @route -- | Capture documentation-instance (HasDocumentApi b, KnownSymbol dRoute, Typeable t) => HasDocumentApi (Capture' m (dRoute :: Symbol) t :> b) where- document r = document @b formatted- where formatted = fold [r, "/", "{", var, "::", format, "}"]- var = symbolVal' @dRoute- format = typeText @t+instance (HasParsableEndpoint b, KnownSymbol dRoute, Typeable t) => HasParsableEndpoint (Capture' m (dRoute :: Symbol) t :> b) where+ parseEndpoint r = parseEndpoint @b formatted+ where formatted = fold [r, "/", "{", var, "::", format, "}"]+ var = symbolVal' @dRoute+ format = typeText @t -- | CaptureAll documentation-instance (HasDocumentApi b, KnownSymbol dRoute, Typeable t) => HasDocumentApi (CaptureAll (dRoute :: Symbol) t :> b) where- document r = document @b formatted- where formatted = fold [r, "/", "{", var, "::", format, "}"]- var = symbolVal' @dRoute- format = typeText @t+instance (HasParsableEndpoint b, KnownSymbol dRoute, Typeable t) => HasParsableEndpoint (CaptureAll (dRoute :: Symbol) t :> b) where+ parseEndpoint r = parseEndpoint @b formatted+ where formatted = fold [r, "/", "{", var, "::", format, "}"]+ var = symbolVal' @dRoute+ format = typeText @t -- | Request HttpVersion documentation-instance HasDocumentApi b => HasDocumentApi (HttpVersion :> b) where- document r a = document @b r $ a <> [("Captures Http Version", Detail "True")]+instance HasParsableEndpoint b => HasParsableEndpoint (HttpVersion :> b) where+ parseEndpoint r a = parseEndpoint @b r $ a <> [("Captures Http Version", Detail "True")] -- | IsSecure documentation-instance HasDocumentApi b => HasDocumentApi (IsSecure :> b) where- document r a = document @b r $ a <> [("SSL Only", Detail "True")]+instance HasParsableEndpoint b => HasParsableEndpoint (IsSecure :> b) where+ parseEndpoint r a = parseEndpoint @b r $ a <> [("SSL Only", Detail "True")] -- | Request Remote host documentation-instance HasDocumentApi b => HasDocumentApi (RemoteHost :> b) where- document r a = document @b r $ a <> [("Captures RemoteHost/IP", Detail "True")]+instance HasParsableEndpoint b => HasParsableEndpoint (RemoteHost :> b) where+ parseEndpoint r a = parseEndpoint @b r $ a <> [("Captures RemoteHost/IP", Detail "True")] -- | Description documentation-instance (HasDocumentApi b, KnownSymbol desc) => HasDocumentApi (Description (desc :: Symbol) :> b) where- document r a = document @b r $ a <> [("Description", Detail $ symbolVal' @desc)]+instance (HasParsableEndpoint b, KnownSymbol desc) => HasParsableEndpoint (Description (desc :: Symbol) :> b) where+ parseEndpoint r a = parseEndpoint @b r $ a <> [("Description", Detail $ symbolVal' @desc)] -- | Summary documentation-instance (HasDocumentApi b, KnownSymbol s) => HasDocumentApi (Summary (s :: Symbol) :> b) where- document r a = document @b r $ a <> [("Summary", Detail $ symbolVal' @s)]+instance (HasParsableEndpoint b, KnownSymbol s) => HasParsableEndpoint (Summary (s :: Symbol) :> b) where+ parseEndpoint r a = parseEndpoint @b r $ a <> [("Summary", Detail $ symbolVal' @s)] -- | Vault documentation-instance HasDocumentApi b => HasDocumentApi (Vault :> b) where- document r a = document @b r $ a <> [("Vault", Detail "True")]+instance HasParsableEndpoint b => HasParsableEndpoint (Vault :> b) where+ parseEndpoint r a = parseEndpoint @b r $ a <> [("Vault", Detail "True")] -- | Basic authentication documentation-instance (HasDocumentApi b, KnownSymbol realm, Typeable a) => HasDocumentApi (BasicAuth (realm :: Symbol) a :> b) where- document r a = document @b r $ a <> [( "Basic Authentication"+instance (HasParsableEndpoint b, KnownSymbol realm, Typeable a) => HasParsableEndpoint (BasicAuth (realm :: Symbol) a :> b) where+ parseEndpoint r a = parseEndpoint @b r $ a <> [( "Basic Authentication" , toDetails [ ("Realm", Detail realm) , ("UserData", Detail userData) ]@@ -162,51 +162,51 @@ userData = typeText @a -- | Authentication documentation-instance (HasDocumentApi b, KnownSymbol token) => HasDocumentApi (AuthProtect (token :: Symbol) :> b) where- document r a = document @b r $ a <> [("Authentication", Detail authDoc)]+instance (HasParsableEndpoint b, KnownSymbol token) => HasParsableEndpoint (AuthProtect (token :: Symbol) :> b) where+ parseEndpoint r a = parseEndpoint @b r $ a <> [("Authentication", Detail authDoc)] where authDoc = symbolVal' @token -- | Request header documentation-instance (HasDocumentApi b, KnownSymbol ct, Typeable typ) => HasDocumentApi (Header' m (ct :: Symbol) typ :> b) where- document r a = document @b r $ a <> [( "RequestHeaders"+instance (HasParsableEndpoint b, KnownSymbol ct, Typeable typ) => HasParsableEndpoint (Header' m (ct :: Symbol) typ :> b) where+ parseEndpoint r a = parseEndpoint @b r $ a <> [( "RequestHeaders" , toDetails [ ("Name", Detail $ symbolVal' @ct) , ("ContentType", Detail $ typeText @typ) ] )] -- | Query flag documentation-instance (HasDocumentApi b, KnownSymbol param) => HasDocumentApi (QueryFlag (param :: Symbol) :> b) where- document r a = document @b r $ a <> [( "QueryFlag"+instance (HasParsableEndpoint b, KnownSymbol param) => HasParsableEndpoint (QueryFlag (param :: Symbol) :> b) where+ parseEndpoint r a = parseEndpoint @b r $ a <> [( "QueryFlag" , toDetails [ ("Param", Detail $ symbolVal' @param) ] )] -- | Query param documentation-instance (HasDocumentApi b, KnownSymbol param, Typeable typ) => HasDocumentApi (QueryParam' m (param :: Symbol) typ :> b) where- document r a = document @b r $ a <> [( "QueryParam"+instance (HasParsableEndpoint b, KnownSymbol param, Typeable typ) => HasParsableEndpoint (QueryParam' m (param :: Symbol) typ :> b) where+ parseEndpoint r a = parseEndpoint @b r $ a <> [( "QueryParam" , toDetails [ ("Param", Detail $ symbolVal' @param) , ("ContentType", Detail $ typeText @typ) ] )] -- | Query params documentation-instance (HasDocumentApi b, KnownSymbol param, Typeable typ) => HasDocumentApi (QueryParams (param :: Symbol) typ :> b) where- document r a = document @b r $ a <> [( "QueryParams"+instance (HasParsableEndpoint b, KnownSymbol param, Typeable typ) => HasParsableEndpoint (QueryParams (param :: Symbol) typ :> b) where+ parseEndpoint r a = parseEndpoint @b r $ a <> [( "QueryParams" , toDetails [ ("Param", Detail $ symbolVal' @param) , ("ContentType", Detail $ typeText @typ) ] )] -- | Request body documentation-instance (HasDocumentApi b, Typeable ct, Typeable typ) => HasDocumentApi (ReqBody' m ct typ :> b) where- document r a = document @b r $ a <> [( "RequestBody"+instance (HasParsableEndpoint b, Typeable ct, Typeable typ) => HasParsableEndpoint (ReqBody' m ct typ :> b) where+ parseEndpoint r a = parseEndpoint @b r $ a <> [( "RequestBody" , toDetails [ ("Format", Detail $ typeText @ct) , ("ContentType", Detail $ typeText @typ) ] )] -- | Stream body documentation-instance (HasDocumentApi b, Typeable ct, Typeable typ) => HasDocumentApi (StreamBody' m ct typ :> b) where- document r a = document @b r $ a <> [( "StreamBody"+instance (HasParsableEndpoint b, Typeable ct, Typeable typ) => HasParsableEndpoint (StreamBody' m ct typ :> b) where+ parseEndpoint r a = parseEndpoint @b r $ a <> [( "StreamBody" , toDetails [ ("Format", Detail $ typeText @ct) , ("ContentType", Detail $ typeText @typ) ]@@ -215,8 +215,8 @@ -- | Response documentation -- Terminates here as responses are last parts of api endpoints -- Note that request type information (GET, POST etc...) is contained here-instance (Typeable m, Typeable ct, Typeable typ) => HasDocumentApi (Verb m s ct typ) where- document r a = ( r+instance (Typeable m, Typeable ct, Typeable typ) => HasParsableEndpoint (Verb m s ct typ) where+ parseEndpoint r a = ( r , fromList $ a <> [requestType, response] ) where requestType = ("RequestType", Detail $ typeText @m)
src/Servant/Docs/Simple/Render.hs view
@@ -72,6 +72,7 @@ , Parameter , Route , Json (..)+ , Markdown (..) , Pretty (..) , PlainText (..) ) where@@ -81,7 +82,9 @@ import Data.List (intersperse) import Data.Map.Ordered (OMap, assocs) import Data.Text (Text, pack)-import Data.Text.Prettyprint.Doc (Doc, cat, line, nest, pretty, vcat, vsep)+import Data.Text.Prettyprint.Doc (Doc, annotate, defaultLayoutOptions, indent, layoutPretty, line,+ pretty, vcat, vsep)+import Data.Text.Prettyprint.Doc.Render.Util.StackMachine (renderSimplyDecorated) -- | Intermediate documentation structure, a hashmap of endpoints --@@ -169,27 +172,64 @@ toJSON (Details ls) = toJSON . fromList . assocs $ ls -- | Conversion to prettyprint-newtype Pretty ann = Pretty { getPretty :: Doc ann }+newtype Pretty = Pretty { getPretty :: Doc Ann } +-- | Annotates our route and parameter keys+data Ann = AnnRoute | AnnParam | AnnDetail+ -- | Conversion to prettyprint-instance Renderable (Pretty ann) where+instance Renderable Pretty where render = Pretty . prettyPrint -- | Helper function to prettyprint the ApiDocs-prettyPrint :: ApiDocs -> Doc ann-prettyPrint (ApiDocs endpoints) = vcat . intersperse line- $ uncurry (toDoc 0) <$> assocs endpoints+prettyPrint :: ApiDocs -> Doc Ann+prettyPrint (ApiDocs endpoints) = vsep+ $ intersperse line+ $ documentRoute+ <$> assocs endpoints --- | Helper function-toDoc :: Int -> Text -> Details -> Doc ann-toDoc i t d = case d of- Detail a -> cat [pretty t, ": ", pretty a]- Details as -> nest i . vsep $ pretty t <> ":"- : (uncurry (toDoc (i + 4)) <$> assocs as)+-- | Documents an API route+documentRoute :: (Route, Details) -- ^ Route-Details pair+ -> Doc Ann -- ^ documentation for Route-Details pair+documentRoute (r, d) = routeDoc <> ":" <> detailsDoc+ where routeDoc = annotate AnnRoute $ pretty r+ detailsDoc = documentDetails 0 d +-- | Documents Details of an API route+documentDetails :: Int -- ^ Indentation+ -> Details -- ^ Details+ -> Doc Ann -- ^ documentation for Details+documentDetails i d = case d of+ Detail d' -> " " <> annotate AnnDetail (pretty d')+ Details ds -> (line <>)+ $ indent i+ $ vcat+ $ documentParameters <$> ds'+ where ds' = assocs ds+ documentParameters (param, details) = annotate AnnParam (pretty param)+ <> ":"+ <> documentDetails (i + 4) details+ -- | Conversion to plaintext newtype PlainText = PlainText { getPlainText :: Text } deriving stock (Eq, Show) -- | Conversion to plaintext instance Renderable PlainText where render = PlainText . pack . show . getPretty . render++-- | Conversion to markdown+newtype Markdown = Markdown { getMarkdown :: Text } deriving stock (Eq, Show)++instance Renderable Markdown where+ render docs = Markdown m+ where m = renderSimplyDecorated id annOpen annClose docStream+ annOpen = \case+ AnnRoute -> "### "+ AnnParam -> "- **"+ AnnDetail -> "`"+ annClose = \case+ AnnRoute -> ""+ AnnParam -> "**"+ AnnDetail -> "`"+ docStream = layoutPretty defaultLayoutOptions docs'+ docs' = getPretty $ render docs
test/Test/Servant/Docs/Simple/Parse.hs view
@@ -10,15 +10,15 @@ import Test.Servant.Docs.Simple.Samples (ApiComplete, ApiMultiple, apiCompleteParsed, apiMultipleParsed) -import Servant.Docs.Simple.Parse (parse)+import Servant.Docs.Simple.Parse (parseApi) import Servant.Docs.Simple.Render (ApiDocs (..)) parseSpec :: Spec parseSpec = describe "Parses API to Document Tree" $ do it "parses an EmptyAPI Details" $- parse @EmptyAPI `shouldBe` ApiDocs empty+ parseApi @EmptyAPI `shouldBe` ApiDocs empty it "parses all Servant API Combinators" $- parse @ApiComplete `shouldBe` apiCompleteParsed+ parseApi @ApiComplete `shouldBe` apiCompleteParsed it "parses an API with multiple endpoints" $- parse @ApiMultiple `shouldBe` apiMultipleParsed+ parseApi @ApiMultiple `shouldBe` apiMultipleParsed