{-# LANGUAGE UndecidableInstances #-}
module Servant.Docs.Simple.Parse
( HasParsableEndpoint (..)
, HasParsableApi (..)
, symbolVal'
, toDetails
, typeText
) where
import Data.Foldable (fold)
import Data.Map.Ordered (OMap, empty, fromList, (|<))
import Data.Proxy
import Data.Text (Text, pack)
import Data.Typeable (Typeable, typeRep)
import GHC.TypeLits (KnownSymbol, Symbol, symbolVal)
import Servant.API ((:>), AuthProtect, BasicAuth, Capture', CaptureAll, Description, EmptyAPI,
Header', HttpVersion, IsSecure, QueryFlag, QueryParam', QueryParams, RemoteHost,
ReqBody', StreamBody', Summary, Vault, Verb)
import qualified Servant.API.TypeLevel as S (Endpoints)
import Servant.Docs.Simple.Render (ApiDocs (..), Details (..), Parameter, Route)
class HasParsableApi api where
parseApi :: ApiDocs
instance HasCollatable (S.Endpoints a) => HasParsableApi a where
parseApi = collate @(S.Endpoints a)
instance {-# OVERLAPPING #-} HasParsableApi EmptyAPI where
parseApi = collate @'[]
class HasCollatable api where
collate :: ApiDocs
instance (HasParsableEndpoint e, HasCollatable b) => HasCollatable (e ': b) where
collate = ApiDocs $ (Details <$> documentEndpoint @e) |< previous
where ApiDocs previous = collate @b
instance HasCollatable '[] where
collate = ApiDocs empty
documentEndpoint :: forall a. HasParsableEndpoint a => (Route, OMap Parameter Details)
documentEndpoint = parseEndpoint @a "" []
class HasParsableEndpoint e where
parseEndpoint :: Route
-> [(Parameter, Details)]
-> (Route, OMap Parameter Details)
instance (HasParsableEndpoint b, KnownSymbol route) => HasParsableEndpoint ((route :: Symbol) :> b) where
parseEndpoint r = parseEndpoint @b formatted
where formatted = fold [r, "/", fragment]
fragment = symbolVal' @route
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
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
instance HasParsableEndpoint b => HasParsableEndpoint (HttpVersion :> b) where
parseEndpoint r a = parseEndpoint @b r $ a <> [("Captures Http Version", Detail "True")]
instance HasParsableEndpoint b => HasParsableEndpoint (IsSecure :> b) where
parseEndpoint r a = parseEndpoint @b r $ a <> [("SSL Only", Detail "True")]
instance HasParsableEndpoint b => HasParsableEndpoint (RemoteHost :> b) where
parseEndpoint r a = parseEndpoint @b r $ a <> [("Captures RemoteHost/IP", Detail "True")]
instance (HasParsableEndpoint b, KnownSymbol desc) => HasParsableEndpoint (Description (desc :: Symbol) :> b) where
parseEndpoint r a = parseEndpoint @b r $ a <> [("Description", Detail $ symbolVal' @desc)]
instance (HasParsableEndpoint b, KnownSymbol s) => HasParsableEndpoint (Summary (s :: Symbol) :> b) where
parseEndpoint r a = parseEndpoint @b r $ a <> [("Summary", Detail $ symbolVal' @s)]
instance HasParsableEndpoint b => HasParsableEndpoint (Vault :> b) where
parseEndpoint r a = parseEndpoint @b r $ a <> [("Vault", Detail "True")]
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)
]
)]
where realm = symbolVal' @realm
userData = typeText @a
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
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)
]
)]
instance (HasParsableEndpoint b, KnownSymbol param) => HasParsableEndpoint (QueryFlag (param :: Symbol) :> b) where
parseEndpoint r a = parseEndpoint @b r $ a <> [( "QueryFlag"
, toDetails [ ("Param", Detail $ symbolVal' @param) ]
)]
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)
]
)]
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)
]
)]
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)
]
)]
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)
]
)]
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)
response = ( "Response"
, toDetails [ ("Format", Detail $ typeText @ct)
, ("ContentType", Detail $ typeText @typ)
]
)
toDetails :: [(Text, Details)] -> Details
toDetails = Details . fromList
typeText :: forall a. (Typeable a) => Text
typeText = pack . show . typeRep $ Proxy @a
symbolVal' :: forall n. KnownSymbol n => Text
symbolVal' = pack . symbolVal $ Proxy @n