mig-0.2.1.0: src/Mig/Core/Class/Route.hs
{-# LANGUAGE UndecidableInstances #-}
{-| Creation of routes from functions. A route is a handler function
for single path of the server.
-}
module Mig.Core.Class.Route (
Route (..),
ToRoute (..),
toRoute,
) where
import Control.Monad.IO.Class
import Data.OpenApi (ToParamSchema (..), ToSchema (..))
import Data.Proxy
import Data.String
import Data.Text (Text)
import GHC.TypeLits
import Mig.Core.Class.MediaType
import Mig.Core.Class.Monad
import Mig.Core.Class.Response (IsResp (..))
import Mig.Core.ServerFun
import Mig.Core.Types
import Web.FormUrlEncoded (FromForm)
import Web.HttpApiData
{-| Values that represent routes.
A route is a function of arbitrary number of arguments. Where
each argument is one of the special newtype-wrappers that
read type-safe information from HTTP-request and return type of the route function
is a value of something convertible to HTTP-request.
-}
class (MonadIO (MonadOf a)) => ToRoute a where
-- | Update API info
toRouteInfo :: RouteInfo -> RouteInfo
-- | Convert to route
toRouteFun :: a -> ServerFun (MonadOf a)
-- | Route contains API-info and how to run it
data Route m = Route
{ info :: RouteInfo
-- ^ definition of the API (to use it in OpenApi or clients)
, run :: ServerFun m
-- ^ how to run a server
}
-- | converts route-like value to route.
toRoute :: forall a. (ToRoute a) => a -> Route (MonadOf a)
toRoute a =
Route
{ info = toRouteInfo @a emptyRouteInfo
, run = toRouteFun a
}
-------------------------------------------------------------------------------------
-- identity instances
instance (MonadIO m) => ToRoute (Route m) where
toRouteInfo = id
toRouteFun = (.run)
-------------------------------------------------------------------------------------
-- request inputs
instance (ToSchema a, FromReqBody media a, ToRoute b) => ToRoute (Body media a -> b) where
toRouteInfo = addBodyInfo @media @a . toRouteInfo @b
toRouteFun f = withBody @media (toRouteFun . f . Body)
instance (FromHttpApiData a, ToParamSchema a, ToRoute b, KnownSymbol sym) => ToRoute (Query sym a -> b) where
toRouteInfo = addQueryInfo @sym @a . toRouteInfo @b
toRouteFun f = withQuery (getName @sym) (toRouteFun . f . Query)
instance (FromHttpApiData a, ToParamSchema a, ToRoute b, KnownSymbol sym) => ToRoute (Optional sym a -> b) where
toRouteInfo = addOptionalInfo @sym @a . toRouteInfo @b
toRouteFun f = withOptional (getName @sym) (toRouteFun . f . Optional)
instance (ToRoute b, KnownSymbol sym) => ToRoute (QueryFlag sym -> b) where
toRouteInfo = addQueryFlagInfo @sym . toRouteInfo @b
toRouteFun f = withQueryFlag (getName @sym) (toRouteFun . f . QueryFlag)
instance (FromHttpApiData a, ToParamSchema a, ToRoute b, KnownSymbol sym) => ToRoute (Capture sym a -> b) where
toRouteInfo = addCaptureInfo @sym @a . toRouteInfo @b
toRouteFun f = withCapture (getName @sym) (toRouteFun . f . Capture)
instance (FromHttpApiData a, ToParamSchema a, ToRoute b, KnownSymbol sym) => ToRoute (Header sym a -> b) where
toRouteInfo = addHeaderInfo @sym @a . toRouteInfo @b
toRouteFun f = withHeader (getName @sym) (toRouteFun . f . Header)
instance (FromHttpApiData a, ToParamSchema a, ToRoute b, KnownSymbol sym) => ToRoute (OptionalHeader sym a -> b) where
toRouteInfo = addOptionalHeaderInfo @sym @a . toRouteInfo @b
toRouteFun f = withOptionalHeader (getName @sym) (toRouteFun . f . OptionalHeader)
instance (FromForm a, ToRoute b) => ToRoute (Cookie a -> b) where
toRouteInfo = addOptionalHeaderInfo @"Cookie" @Text . toRouteInfo @b
toRouteFun f = withCookie (toRouteFun . f . Cookie)
instance (ToRoute b) => ToRoute (PathInfo -> b) where
toRouteInfo = toRouteInfo @b
toRouteFun f = withPathInfo (toRouteFun . f . PathInfo)
instance (ToRoute b) => ToRoute (FullPathInfo -> b) where
toRouteInfo = toRouteInfo @b
toRouteFun f = withFullPathInfo (toRouteFun . f . FullPathInfo)
instance (ToRoute b) => ToRoute (RawRequest -> b) where
toRouteInfo = toRouteInfo @b
toRouteFun f = \req -> toRouteFun (f (RawRequest req)) req
instance (ToRoute b) => ToRoute (IsSecure -> b) where
toRouteInfo = toRouteInfo @b
toRouteFun f = \req -> toRouteFun (f (IsSecure req.isSecure)) req
-------------------------------------------------------------------------------------
-- outputs
instance {-# OVERLAPPABLE #-} (MonadIO m, IsResp a, IsMethod method) => ToRoute (Send method m a) where
toRouteInfo = setMethod (toMethod @method) (getMedia @a)
toRouteFun (Send a) = sendResponse $ toResponse <$> a
---------------------------------------------
-- utils
getName :: forall sym a. (KnownSymbol sym, IsString a) => a
getName = fromString (symbolVal (Proxy @sym))