servant-util-0.1.0: src/Servant/Util/Stats.hs
-- | Verious information about your API.
module Servant.Util.Stats
( methodsCoveringAPI
) where
import Universum
import GHC.TypeLits (ErrorMessage (..), Symbol, TypeError)
import Network.HTTP.Types.Method (Method, StdMethod)
import Servant (EmptyAPI, Raw, ReflectMethod (..), Verb, (:<|>), (:>))
-------------------------------------------------------------------------
-- CORS methods coherence
-------------------------------------------------------------------------
-- | Whether a given type item is element of a given list.
type family IsElemBool (x :: StdMethod) (l :: [StdMethod]) :: Bool where
IsElemBool x (x : _) = 'True
IsElemBool x (y : xs) = IsElemBool x xs
IsElemBool x '[] = 'False
type family FailOnDissallowedMethod (method :: StdMethod) (allowed :: Bool) :: Constraint where
FailOnDissallowedMethod _ 'True = ()
FailOnDissallowedMethod m 'False = TypeError
( 'Text "Method " ':$$: 'ShowType m ':$$: 'Text " is not allowed, but appears in API"
)
-- | Ensure that the given api uses only methods from the list provided.
type family ContainsOnlyMethods (methods :: [StdMethod]) api :: Constraint where
ContainsOnlyMethods ms ((path :: Symbol) :> sub) = ContainsOnlyMethods ms sub
ContainsOnlyMethods ms (part :> sub) = ContainsOnlyMethods ms sub
ContainsOnlyMethods ms (api1 :<|> api2) = (ContainsOnlyMethods ms api1,
ContainsOnlyMethods ms api2)
ContainsOnlyMethods ms (Verb m _ _ _) = FailOnDissallowedMethod m (IsElemBool m ms)
ContainsOnlyMethods ms Raw = ()
ContainsOnlyMethods ms EmptyAPI = ()
-- | 'ReflectMethod' lifted to method lists.
class ReflectMethods (methods :: [StdMethod]) where
reflectMethods :: Proxy methods -> [Method]
instance ReflectMethods '[] where
reflectMethods _ = []
instance (ReflectMethod m, ReflectMethods ms) => ReflectMethods (m ': ms) where
reflectMethods _ = reflectMethod @m Proxy : reflectMethods @ms Proxy
-- | For the given list of methods, ensure only they are used in API, and get corresponding
-- 'Method' terms.
--
-- A primary use case for this function is specifying CORS methods where we need to think
-- about each single method we allow, thus expecting methods list to be specified manually.
methodsCoveringAPI
:: forall methods api.
(ContainsOnlyMethods methods api, ReflectMethods methods)
=> [Method]
methodsCoveringAPI = reflectMethods @methods Proxy