rfc-0.0.0.21: src/RFC/Servant/ApiDoc.hs
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE UndecidableInstances #-}
module RFC.Servant.ApiDoc
( apiToHtml
, apiToAscii
, apiToSwagger
, apiApplication
, swaggerSchemaOptions
, ToSchemaRFC
) where
import qualified Data.Aeson as Aeson
import Data.Aeson.Types (fromEncoding, toEncoding)
import qualified Data.Binary.Builder as Builder
import Data.Char as Char
import Data.Default (def)
import Data.Monoid ((<>))
import Data.Swagger
import Data.Swagger.Declare
import Data.Swagger.Internal.Schema (GToSchema)
import Data.Swagger.Internal.TypeShape (TypeHasSimpleShape)
import GHC.Generics (Rep)
import Network.HTTP.Types.Header (hContentType)
import Network.HTTP.Types.Status
import Network.Wai
import RFC.JSON (jsonOptions)
import RFC.Prelude hiding ((<>))
import RFC.Servant
import RFC.String ()
import Servant.Swagger
import qualified Text.Blaze.Html.Renderer.String as Blaze
import qualified Text.Markdown as MD
apiToHtml :: (HasDocs a) => Proxy a -> Html
apiToHtml = preEscapedToHtml . (MD.markdown mdSettings) . cs . markdown . docs
where
mdSettings = def
{ MD.msLinkNewTab = False
, MD.msAddHeadingId = True
}
apiToAscii :: (ConvertibleString String s, HasDocs a) => Proxy a -> s
apiToAscii = cs . markdown . docs
apiToSwagger :: (HasSwagger a) => Proxy a -> Swagger
apiToSwagger = toSwagger
apiApplication :: (HasDocs a, HasSwagger a) => Proxy a -> Swagger -> Application
apiApplication api addlSwagger request callback =
case reqMethod of
"GET" -> checkPath
_ -> failMethodNotAllowed
where
html = Blaze.renderHtml $ apiToHtml api
ascii :: LazyByteString
ascii = apiToAscii api
swaggerToLbs :: Swagger -> LazyByteString
swaggerToLbs = Builder.toLazyByteString . fromEncoding . toEncoding
swagger = swaggerToLbs $ apiToSwagger api <> addlSwagger
reqMethod :: String
reqMethod = map Char.toUpper $ cs $ requestMethod request
pathInfo :: String
pathInfo = map Char.toLower $ cs $ rawPathInfo request
checkPath =
case pathInfo of
"swagger.json" -> serveSwagger
"/swagger.json" -> serveSwagger
"api.html" -> serveHtml
"/api.html" -> serveHtml
"api.txt" -> serveTxt
"/api.txt" -> serveTxt
_ -> failPathNotFound
response ::
(ConvertibleStrings contentType StrictByteString, ConvertibleStrings body LazyByteString) =>
contentType -> body -> IO ResponseReceived
response contentType body = callback $
responseLBS status200 [(hContentType, cs contentType)] (cs body)
serveHtml = response "text/html" html
serveTxt = response "text/plain" ascii
serveSwagger = response "application/json" swagger
failMethodNotAllowed :: IO ResponseReceived
failMethodNotAllowed = callback $
responseLBS status405 [(hContentType, cs "text/plain")] (cs $ "Unsupported HTTP method: " ++ reqMethod)
failPathNotFound :: IO ResponseReceived
failPathNotFound = callback $
responseLBS status404 [(hContentType, cs "text/plain")] (cs $ "Path not found: " ++ pathInfo)
instance ToSample () where
toSamples _ = [(cs "No value", ())]
swaggerSchemaOptions :: SchemaOptions
swaggerSchemaOptions = SchemaOptions
{ fieldLabelModifier = Aeson.fieldLabelModifier jsonOptions
, constructorTagModifier = Aeson.constructorTagModifier jsonOptions
, unwrapUnaryRecords = Aeson.unwrapUnaryRecords jsonOptions
, datatypeNameModifier = id
, allNullaryToStringTag = True
}
class ToSchemaRFC a where
declareNamedSchemaRFC :: proxy a -> Declare (Definitions Schema) NamedSchema
default declareNamedSchemaRFC ::
(Generic a
, GToSchema (Rep a)
, TypeHasSimpleShape a "genericDeclareNamedSchemaUnrestricted"
) => proxy a -> Declare (Definitions Schema) NamedSchema
declareNamedSchemaRFC = genericDeclareNamedSchema swaggerSchemaOptions
instance {-# OVERLAPPABLE #-} ToSchemaRFC a => ToSchema a where
declareNamedSchema = declareNamedSchemaRFC