{- | Renders the intermediate structure into common documentation formats __Example scripts__ [Generating plaintext/JSON documentation from api types](https://github.com/Holmusk/servant-docs-simple/blob/master/examples/generate.hs) [Writing our own rendering format](https://github.com/Holmusk/servant-docs-simple/blob/master/examples/render.hs) __Example of rendering the intermediate structure__ /Intermediate structure/ > ApiDocs ( fromList [( "/hello/world", > , Details (fromList ([ ( "RequestBody" > , Details (fromList ([ ( "Format" > , Detail "': * () ('[] *)" > ) > , ( "ContentType" > , Detail "()" > ) > ])) > ) > , ( "RequestType" > , Detail "'POST" > ) > , ( "Response" > , Details (fromList ([ ( "Format" > , Detail "': * () ('[] *)" > ) > , ( "ContentType" > , Detail "()" > ) > ])) > ) > ])) > )]) /JSON/ > { > "/hello/world": { > "Response": { > "Format": "': * () ('[] *)", > "ContentType": "()" > }, > "RequestType": "'POST", > "RequestBody": { > "Format": "': * () ('[] *)", > "ContentType": "()" > } > } > } /Text/ > /hello/world: > RequestBody: > Format: ': * () ('[] *) > ContentType: () > RequestType: 'POST > Response: > Format: ': * () ('[] *) > ContentType: () -} module Servant.Docs.Simple.Render ( ApiDocs (..) , Details (..) , Renderable (..) , Parameter , Route , Json (..) , Markdown (..) , Pretty (..) , PlainText (..) ) where import Data.Aeson (ToJSON (..), Value (..)) import Data.HashMap.Strict (fromList) import Data.List (intersperse) import Data.Map.Ordered (OMap, assocs) import Data.Text (Text, pack) 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 -- -- API type: -- -- > type API = "users" :> ( "update" :> Response '[()] () -- > :<|> "get" :> Response '[()] () -- > ) -- -- Parsed into ApiDocs: -- -- -- > ApiDocs ( fromList [ ( "/users/update", -- > , Details (fromList ([ ( "Response" -- > , Details (fromList ([ ( "Format" -- > , Detail "': * () ('[] *)" -- > ) -- > , ( "ContentType" -- > , Detail "()" -- > ) -- > ])) -- > ) -- > ])) -- > ) -- > , ( "/users/get", -- > , Details (fromList ([ ( "Response" -- > , Details (fromList ([ ( "Format" -- > , Detail "': * () ('[] *)" -- > ) -- > , ( "ContentType" -- > , Detail "()" -- > ) -- > ])) -- > ) -- > ])) -- > ) -- > ]) -- -- For more examples reference [Test.Servant.Docs.Simple.Samples](https://github.com/Holmusk/servant-docs-simple/blob/master/test/Test/Servant/Docs/Simple/Samples.hs) -- newtype ApiDocs = ApiDocs (OMap Route Details) deriving stock (Eq, Show) -- | Route representation type Route = Text -- | Details of the Api Route -- -- __Examples__ -- -- > Authentication: true -- -- Can be interpreted as a Parameter (Authentication) and a /Detail/ (true) -- -- > Response: -- > Format: ... -- > ContentType: ... -- -- Can be interpreted as a Parameter (Response) and /Details/ (Format (...), ContentType (...)) -- data Details = Details (OMap Parameter Details) -- ^ OMap of Parameter-Details | Detail Text -- ^ Single Value deriving stock (Eq, Show) -- | Parameter names type Parameter = Text -- | Convert ApiDocs into different documentation formats class Renderable a where render :: ApiDocs -> a -- | Conversion to JSON using Data.Aeson newtype Json = Json { getJson :: Value } deriving stock (Eq, Show) -- | Conversion to JSON using Data.Aeson instance Renderable Json where render = Json . toJSON -- | Json instance for the endpoints hashmap instance ToJSON ApiDocs where toJSON (ApiDocs endpoints) = toJSON . fromList . assocs $ endpoints -- | Json instance for the parameter hashmap of each endpoint instance ToJSON Details where toJSON (Detail t) = String t toJSON (Details ls) = toJSON . fromList . assocs $ ls -- | Conversion to prettyprint newtype Pretty = Pretty { getPretty :: Doc Ann } -- | Annotates our route and parameter keys data Ann = AnnRoute | AnnParam | AnnDetail -- | Conversion to prettyprint instance Renderable Pretty where render = Pretty . prettyPrint -- | Helper function to prettyprint the ApiDocs prettyPrint :: ApiDocs -> Doc Ann prettyPrint (ApiDocs endpoints) = vsep $ intersperse line $ documentRoute <$> assocs endpoints -- | 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