{-# language AllowAmbiguousTypes #-} {-# language CPP #-} {-# language ConstraintKinds #-} {-# language DataKinds #-} {-# language ExistentialQuantification #-} {-# language FlexibleContexts #-} {-# language FlexibleInstances #-} {-# language FunctionalDependencies #-} {-# language GADTs #-} {-# language PatternSynonyms #-} {-# language PolyKinds #-} {-# language RankNTypes #-} {-# language ScopedTypeVariables #-} {-# language TypeApplications #-} {-# language TypeFamilies #-} {-# language TypeOperators #-} {-# language UndecidableInstances #-} {-# language ViewPatterns #-} {-| Description : Protocol-independent declaration of servers. A server (represented by 'ServerT') is a sequence of handlers (represented by 'HandlersT'), one for each operation in the corresponding Mu service declaration. In general, you can declare a server by naming each of the methods with their handlers: > server :: MonadServer m => ServerT MyService m _ > server = singleService ( method @"m1" h1 > , method @"m2" h2 > , ... ) or by position: > server :: MonadServer m => ServerT MyService m _ > server = Server (h1 :<|>: h2 :<|>: ... :<|>: H0) where each of @h1@, @h2@, ... handles each method in @MyService@ /in the order they were declared/. In both cases, the @_@ in the type allows GHC to fill in the boring and long type you would need to write there otherwise. /Implementation note/: exceptions raised in handlers produce an error to be sent as response to the client. We recommend you to catch exceptions and return custom 'ServerError's instead. -} module Mu.Server ( -- * Servers and handlers MonadServer, ServiceChain, noContext , wrapServer -- ** Definitions by name , singleService , method, methodWithInfo , resolver, object, union , field, fieldWithInfo , UnionChoice(..), unionChoice , NamedList(..) -- ** Definitions by position , SingleServerT, pattern Server , ServerT(..), ServicesT(..), ServiceT(..), HandlersT(.., (:<||>:), (:<|>:)) -- ** Simple servers using only IO , ServerErrorIO, ServerIO -- * Errors which might be raised , serverError, ServerError(..), ServerErrorCode(..) -- ** Useful when you do not want to deal with errors , alwaysOk -- * For internal use , Handles, FromRef, ToRef ) where import Control.Exception (Exception) import Control.Monad.Except import Data.Conduit import Data.Kind import Data.Typeable import GHC.TypeLits import Mu.Named import Mu.Rpc import Mu.Schema #if __GLASGOW_HASKELL__ < 808 import Unsafe.Coerce (unsafeCoerce) #endif -- | Constraint for monads that can be used as servers type MonadServer m = (MonadError ServerError m, MonadIO m) -- | Simplest monad which satisfies 'MonadServer'. type ServerErrorIO = ExceptT ServerError IO -- | Simple 'ServerT' which uses only 'IO' and errors, -- and whose service has no back-references. type ServerIO info srv = ServerT '[] info srv ServerErrorIO -- | Stop the current handler, -- returning an error to the client. serverError :: (MonadError ServerError m) => ServerError -> m a serverError = throwError -- | Wrapper for handlers which do not use errors. -- Remember that any exception raised in 'IO' -- is propagated to the client. alwaysOk :: (MonadIO m) => IO a -> m a alwaysOk = liftIO -- | To declare that the function doesn't use -- its context. noContext :: b -> a1 -> a2 -> b noContext x _ _ = x -- | Errors raised in a handler. data ServerError = ServerError ServerErrorCode String deriving Show instance Exception ServerError -- | Possible types of errors. -- Some of these are handled in a special way -- by different transpoprt layers. data ServerErrorCode = Unknown | Unavailable | Unimplemented | Unauthenticated | Internal | Invalid | NotFound deriving (Eq, Show) -- | Defines a mapping between outcome of -- a service, and its representation as -- Haskell type. type ServiceChain snm = Mappings snm Type -- | A server for a single service, -- like most RPC ones. type SingleServerT = ServerT '[] -- | Definition of a complete server -- for a set of services, with possible -- references between them. data ServerT (chn :: ServiceChain snm) (info :: Type) (s :: Package snm mnm anm (TypeRef snm)) (m :: Type -> Type) (hs :: [[Type]]) where Services :: ServicesT chn info s m hs -> ServerT chn info ('Package pname s) m hs pattern Server :: (MappingRight chn sname ~ ()) => HandlersT chn info () methods m hs -> ServerT chn info ('Package pname '[ 'Service sname methods ]) m '[hs] pattern Server svr = Services (ProperSvc svr :<&>: S0) infixr 3 :<&>: -- | Definition of a complete server for a service. data ServicesT (chn :: ServiceChain snm) (info :: Type) (s :: [Service snm mnm anm (TypeRef snm)]) (m :: Type -> Type) (hs :: [[Type]]) where S0 :: ServicesT chn info '[] m '[] (:<&>:) :: ServiceT chn info svc m hs -> ServicesT chn info rest m hss -> ServicesT chn info (svc ': rest) m (hs ': hss) type family InUnion (x :: k) (xs :: [k]) :: Constraint where InUnion x '[] = TypeError ('ShowType x ':<>: 'Text " is not part of the union") InUnion x (x ': xs) = () InUnion x (y ': xs) = InUnion x xs data UnionChoice chn elts where UnionChoice :: (InUnion elt elts, Typeable elt) => Proxy elt -> MappingRight chn elt -> UnionChoice chn elts unionChoice :: forall elt elts chn. (InUnion elt elts, Typeable elt) => MappingRight chn elt -> UnionChoice chn elts unionChoice = UnionChoice (Proxy @elt) -- | Definition of different kinds of services. data ServiceT chn info svc m hs where ProperSvc :: HandlersT chn info (MappingRight chn sname) methods m hs -> ServiceT chn info ('Service sname methods) m hs OneOfSvc :: (MappingRight chn sname -> m (UnionChoice chn elts)) -> ServiceT chn info ('OneOf sname elts) m '[] -- | 'HandlersT' is a sequence of handlers. -- Note that the handlers for your service -- must appear __in the same order__ as they -- are defined. -- -- In general you can choose any type you want -- for your handlers, due to the following restrictions: -- -- * Haskell types must be convertible to the -- corresponding schema type. In other words, -- they must implement 'FromSchema' if they are -- inputs, and 'ToSchema' if they are outputs. -- * Normal returns are represented by returning -- the corresponding Haskell type. -- * Input streams turn into @Conduit () t m ()@, -- where @t@ is the Haskell type for that schema type. -- * Output streams turn into an __additional argument__ -- of type @Conduit t Void m ()@. This stream should -- be connected to a source to get the elements. data HandlersT (chn :: ServiceChain snm) (info :: Type) (inh :: *) (methods :: [Method snm mnm anm (TypeRef snm)]) (m :: Type -> Type) (hs :: [Type]) where H0 :: HandlersT chn info inh '[] m '[] Hmore :: Handles chn args ret m h => Proxy args -> Proxy ret -> (RpcInfo info -> inh -> h) -> HandlersT chn info inh ms m hs -> HandlersT chn info inh ('Method name args ret ': ms) m (h ': hs) infixr 4 :<||>: pattern (:<||>:) :: Handles chn args ret m h => (RpcInfo info -> inh -> h) -> HandlersT chn info inh ms m hs -> HandlersT chn info inh ('Method name args ret ': ms) m (h ': hs) pattern x :<||>: xs <- Hmore _ _ x xs where x :<||>: xs = Hmore Proxy Proxy x xs infixr 4 :<|>: pattern (:<|>:) :: (Handles chn args ret m h) => h -> HandlersT chn info () ms m hs -> HandlersT chn info () ('Method name args ret ': ms) m (h ': hs) pattern x :<|>: xs <- (($ ()) . ($ NoRpcInfo) -> x) :<||>: xs where x :<|>: xs = noContext x :<||>: xs -- | Defines a relation for handling. class Handles (chn :: ServiceChain snm) (args :: [Argument snm anm (TypeRef snm)]) (ret :: Return snm (TypeRef snm)) (m :: Type -> Type) (h :: Type) where wrapHandler :: Proxy '(chn, m) -> Proxy args -> Proxy ret -> (forall a. m a -> m a) -> h -> h -- | Defines whether a given type @t@ -- can be turned into the 'TypeRef' @ref@. class ToRef (chn :: ServiceChain snm) (ref :: TypeRef snm) (t :: Type) -- | Defines whether a given type @t@ -- can be obtained from the 'TypeRef' @ref@. class FromRef (chn :: ServiceChain snm) (ref :: TypeRef snm) (t :: Type) -- Type references instance t ~ s => ToRef chn ('PrimitiveRef t) s instance ToSchema sch sty t => ToRef chn ('SchemaRef sch sty) t instance MappingRight chn ref ~ t => ToRef chn ('ObjectRef ref) t instance t ~ s => ToRef chn ('RegistryRef subject t last) s instance (ToRef chn ref t, [t] ~ s) => ToRef chn ('ListRef ref) s instance (ToRef chn ref t, Maybe t ~ s) => ToRef chn ('OptionalRef ref) s instance t ~ s => FromRef chn ('PrimitiveRef t) s instance FromSchema sch sty t => FromRef chn ('SchemaRef sch sty) t instance MappingRight chn ref ~ t => FromRef chn ('ObjectRef ref) t instance t ~ s => FromRef chn ('RegistryRef subject t last) s instance (FromRef chn ref t, [t] ~ s) => FromRef chn ('ListRef ref) s instance (FromRef chn ref t, Maybe t ~ s) => FromRef chn ('OptionalRef ref) s -- Arguments instance forall chn ref args ret m handler h t aname. ( FromRef chn ref t, Handles chn args ret m h , handler ~ (t -> h) ) => Handles chn ('ArgSingle aname ref ': args) ret m handler where wrapHandler pchn _ pr f h = wrapHandler pchn (Proxy @args) pr f . h instance (MonadError ServerError m, FromRef chn ref t, Handles chn args ret m h, handler ~ (ConduitT () t m () -> h)) => Handles chn ('ArgStream aname ref ': args) ret m handler where wrapHandler pchn _ pr f h = wrapHandler pchn (Proxy @args) pr f . h -- Result with exception instance (MonadError ServerError m, handler ~ m ()) => Handles chn '[] 'RetNothing m handler where wrapHandler _ _ _ f h = f h instance ( MonadError ServerError m, ToRef chn eref e, ToRef chn vref v , handler ~ m (Either e v) ) => Handles chn '[] ('RetThrows eref vref) m handler where wrapHandler _ _ _ f h = f h instance (MonadError ServerError m, ToRef chn ref v, handler ~ m v) => Handles chn '[] ('RetSingle ref) m handler where wrapHandler _ _ _ f h = f h instance ( MonadError ServerError m, ToRef chn ref v , handler ~ (ConduitT v Void m () -> m ()) ) => Handles chn '[] ('RetStream ref) m handler where wrapHandler _ _ _ f h = f . h -- SIMPLER WAY TO DECLARE SERVICES -- | Declares the handler for a method in the service. -- Intended to be used with @TypeApplications@: -- -- > method @"myMethod" myHandler method :: forall n a p. p -> Named n (a -> () -> p) method f = Named (\_ _ -> f) -- | Declares the handler for a method in the service, -- which is passed additional information about the call. -- Intended to be used with @TypeApplications@: -- -- > methodWithInfo @"myMethod" myHandler methodWithInfo :: forall n p info. (RpcInfo info -> p) -> Named n (RpcInfo info -> () -> p) methodWithInfo f = Named (\x () -> f x) -- | Declares the handler for a field in an object. -- Intended to be used with @TypeApplications@: -- -- > field @"myField" myHandler field :: forall n h info. h -> Named n (RpcInfo info -> h) field f = Named (const f) -- | Declares the handler for a field in an object, -- which is passed additional information about the call. -- Intended to be used with @TypeApplications@: -- -- > fieldWithInfo @"myField" myHandler fieldWithInfo :: forall n h info. (RpcInfo info -> h) -> Named n (RpcInfo info -> h) fieldWithInfo = Named -- | Defines a server for a package with a single service. -- Intended to be used with a tuple of 'method's: -- -- > singleService (method @"m1" h1, method @"m2" h2) singleService :: ( ToNamedList p nl , ToHandlers chn info () methods m hs nl , MappingRight chn sname ~ () ) => p -> ServerT chn info ('Package pname '[ 'Service sname methods ]) m '[hs] singleService nl = Server $ toHandlers $ toNamedList nl -- | Defines the implementation of a single GraphQL object, -- which translates as a single Mu service. -- Intended to be used with @TypeApplications@ -- and a tuple of 'field's: -- -- > object @"myObject" (field @"f1" h1, fielf @"f2" h2) -- -- Note: for the root objects in GraphQL (query, mutation, subscription) -- use 'method' instead of 'object'. object :: forall sname p nl chn info ms m hs. ( ToNamedList p nl , ToHandlers chn info (MappingRight chn sname) ms m hs nl ) => p -> Named sname (HandlersT chn info (MappingRight chn sname) ms m hs) object nl = Named $ toHandlers $ toNamedList nl union :: forall sname chn m elts. (MappingRight chn sname -> m (UnionChoice chn elts)) -> Named sname (MappingRight chn sname -> m (UnionChoice chn elts)) union = Named -- | Combines the implementation of several GraphQL objects, -- which means a whole Mu service for a GraphQL server. -- Intented to be used with a tuple of 'objects': -- -- > resolver (object @"o1" ..., object @"o2" ...) resolver :: (ToNamedList p nl, ToServices chn info ss m hs nl) => p -> ServerT chn info ('Package pname ss) m hs resolver nl = Services $ toServices $ toNamedList nl class ToHandlers chn info inh ms m hs nl | chn inh ms m nl -> hs where toHandlers :: NamedList nl -> HandlersT chn info inh ms m hs instance ToHandlers chn info inh '[] m '[] nl where toHandlers _ = H0 instance ( FindHandler name info inh h nl , Handles chn args ret m h , ToHandlers chn info inh ms m hs nl ) => ToHandlers chn info inh ('Method name args ret ': ms) m (h ': hs) nl where toHandlers nl = findHandler (Proxy @name) nl :<||>: toHandlers nl class FindHandler name info inh h nl | name nl -> inh h where findHandler :: Proxy name -> NamedList nl -> RpcInfo info -> inh -> h instance (inh ~ h, h ~ TypeError ('Text "cannot find handler for " ':<>: 'ShowType name)) => FindHandler name info inh h '[] where findHandler = error "this should never be called" instance {-# OVERLAPS #-} (RpcInfo info ~ rpc', inh ~ inh', h ~ h') => FindHandler name info inh h ( '(name, rpc' -> inh' -> h') ': rest ) where findHandler _ (Named f :|: _) = f instance {-# OVERLAPPABLE #-} FindHandler name info inh h rest => FindHandler name info inh h (thing ': rest) where findHandler p (_ :|: rest) = findHandler p rest class ToServices chn info ss m hs nl | chn ss m nl -> hs where toServices :: NamedList nl -> ServicesT chn info ss m hs instance ToServices chn info '[] m '[] nl where toServices _ = S0 instance ( FindService name (HandlersT chn info (MappingRight chn name) methods m h) nl , ToServices chn info ss m hs nl) => ToServices chn info ('Service name methods ': ss) m (h ': hs) nl where toServices nl = ProperSvc (findService (Proxy @name) nl) :<&>: toServices nl instance ( FindService name (MappingRight chn name -> m (UnionChoice chn elts)) nl , ToServices chn info ss m hs nl) => ToServices chn info ('OneOf name elts ': ss) m ('[] ': hs) nl where toServices nl = OneOfSvc (findService (Proxy @name) nl) :<&>: toServices nl class FindService name h nl | name nl -> h where findService :: Proxy name -> NamedList nl -> h instance (h ~ TypeError ('Text "cannot find handler for " ':<>: 'ShowType name)) => FindService name h '[] where findService = error "this should never be called" instance {-# OVERLAPS #-} (h ~ h') => FindService name h ( '(name, h') ': rest ) where findService _ (Named f :|: _) = f instance {-# OVERLAPPABLE #-} FindService name h rest => FindService name h (thing ': rest) where findService p (_ :|: rest) = findService p rest -- WRAPPING MECHANISM wrapServer :: forall chn info p m topHs. (forall a. RpcInfo info -> m a -> m a) -> ServerT chn info p m topHs -> ServerT chn info p m topHs wrapServer f (Services ss) = Services (wrapServices ss) where wrapServices :: forall ss hs. ServicesT chn info ss m hs -> ServicesT chn info ss m hs wrapServices S0 = S0 #if __GLASGOW_HASKELL__ >= 808 wrapServices (ProperSvc h :<&>: rest) = ProperSvc (wrapHandlers h) :<&>: wrapServices rest wrapServices (OneOfSvc h :<&>: rest) = OneOfSvc h :<&>: wrapServices rest #else wrapServices (ProperSvc h :<&>: rest) = ProperSvc (unsafeCoerce (wrapHandlers (unsafeCoerce h))) :<&>: unsafeCoerce (wrapServices rest) wrapServices (OneOfSvc h :<&>: rest) = OneOfSvc (unsafeCoerce h) :<&>: unsafeCoerce (wrapServices rest) #endif wrapHandlers :: forall inh ms innerHs. HandlersT chn info inh ms m innerHs -> HandlersT chn info inh ms m innerHs wrapHandlers H0 = H0 wrapHandlers (Hmore pargs pret h rest) = Hmore pargs pret (\rpc inh -> wrapHandler (Proxy @'(chn, m)) pargs pret (f rpc) (h rpc inh)) (wrapHandlers rest)