mu-rpc-0.5.0.0: src/Mu/Rpc.hs
{-# language ConstraintKinds #-}
{-# language DataKinds #-}
{-# language ExistentialQuantification #-}
{-# language FlexibleInstances #-}
{-# language GADTs #-}
{-# language MultiParamTypeClasses #-}
{-# language OverloadedStrings #-}
{-# language PolyKinds #-}
{-# language ScopedTypeVariables #-}
{-# language TypeApplications #-}
{-# language TypeFamilies #-}
{-# language TypeOperators #-}
{-# language UndecidableInstances #-}
{-|
Description : Protocol-independent declaration of services
This module defines a type-level language to describe
RPC-like microservices independently of the transport
and protocol.
-}
module Mu.Rpc (
Package', Package(..)
, Service', Service(..), Object, Union
, Method', Method(..), ObjectField
, LookupService, LookupMethod
, TypeRef(..), Argument', Argument(..), Return(..)
, TyInfo(..), RpcInfo(..), ReflectRpcInfo(..)
) where
import Data.Kind
import Data.Text (Text)
import qualified Data.Text as T
import GHC.TypeLits
import qualified Language.Haskell.TH as TH
import Network.HTTP.Types.Header
import Type.Reflection
import Mu.Schema
import Mu.Schema.Registry
-- | Packages whose names are given by type-level strings.
type Package' = Package Symbol Symbol Symbol (TypeRef Symbol)
-- | Services whose names are given by type-level strings.
type Service' = Service Symbol Symbol Symbol (TypeRef Symbol)
-- | Methods whose names are given by type-level strings.
type Method' = Method Symbol Symbol Symbol (TypeRef Symbol)
-- | Arguments whose names are given by type-level strings.
type Argument' = Argument Symbol Symbol (TypeRef Symbol)
-- | A package is a set of services.
data Package serviceName methodName argName tyRef
= Package (Maybe serviceName)
[Service serviceName methodName argName tyRef]
-- | A service is a set of methods.
data Service serviceName methodName argName tyRef
= Service serviceName
[Method serviceName methodName argName tyRef]
| OneOf serviceName [serviceName]
-- | A method is defined by its name, arguments, and return type.
data Method serviceName methodName argName tyRef
= Method methodName
[Argument serviceName argName tyRef]
(Return serviceName tyRef)
-- Synonyms for GraphQL
-- | An object is a set of fields, in GraphQL lingo.
type Object = 'Service
-- | A union is one of the objects.
type Union = 'OneOf
-- | A field in an object takes some input objects,
-- and returns a value or some other object,
-- in GraphQL lingo.
type ObjectField = 'Method
-- | Look up a service in a package definition using its name.
type family LookupService (ss :: [Service snm mnm anm tr]) (s :: snm)
:: Service snm mnm anm tr where
LookupService '[] s = TypeError ('Text "could not find method " ':<>: 'ShowType s)
LookupService ('Service s ms ': ss) s = 'Service s ms
LookupService ('OneOf s ms ': ss) s = 'OneOf s ms
LookupService (other ': ss) s = LookupService ss s
-- | Look up a method in a service definition using its name.
type family LookupMethod (s :: [Method snm mnm anm tr]) (m :: mnm)
:: Method snm mnm anm tr where
LookupMethod '[] m = TypeError ('Text "could not find method " ':<>: 'ShowType m)
LookupMethod ('Method m args r ': ms) m = 'Method m args r
LookupMethod (other ': ms) m = LookupMethod ms m
-- | Defines a reference to a type, either primitive or coming from the schema.
-- 'TypeRef's are used to define arguments and result types.
data TypeRef serviceName where
-- | A primitive type.
PrimitiveRef :: Type -> TypeRef serviceName
-- | Chain with another service.
ObjectRef :: serviceName -> TypeRef serviceName
-- | Point to schema.
SchemaRef :: Schema typeName fieldName -> typeName -> TypeRef serviceName
-- | Registry subject, type to convert to, and preferred serialization version
RegistryRef :: Registry -> Type -> Nat -> TypeRef serviceName
-- | To be used only during TH generation!
THRef :: TH.Type -> TypeRef serviceName
-- Combinators found in the gRPC and GraphQL languages.
-- | Represents a list of values.
ListRef :: TypeRef serviceName -> TypeRef serviceName
-- | Represents a possibly-missing value.
OptionalRef :: TypeRef serviceName -> TypeRef serviceName
instance Show (TypeRef s) where
show _ = "ty"
-- | Defines the way in which arguments are handled.
data Argument serviceName argName tyRef where
-- | Use a single value.
ArgSingle :: Maybe argName
-> tyRef
-> Argument serviceName argName tyRef
-- | Consume a stream of values.
ArgStream :: Maybe argName
-> tyRef
-> Argument serviceName argName tyRef
-- | Defines the different possibilities for returning
-- information from a method.
data Return serviceName tyRef where
-- | Fire and forget.
RetNothing :: Return serviceName tyRef
-- | Return a single value.
RetSingle :: tyRef -> Return serviceName tyRef
-- | Return a stream of values.
RetStream :: tyRef -> Return serviceName tyRef
-- | Return a value or an error.
RetThrows :: tyRef -> tyRef -> Return serviceName tyRef
-- | Reflection
data RpcInfo i
= NoRpcInfo
| RpcInfo { packageInfo :: Package Text Text Text TyInfo
, serviceInfo :: Service Text Text Text TyInfo
, methodInfo :: Maybe (Method Text Text Text TyInfo)
, headers :: RequestHeaders
, extraInfo :: i
}
data TyInfo
= TyList TyInfo
| TyOption TyInfo
| TyTy Text
deriving (Show, Eq)
instance Show (RpcInfo i) where
show NoRpcInfo
= "<no info>"
show (RpcInfo (Package p _) s m _ _)
= T.unpack $ showPkg p (showMth m (showSvc s))
where
showPkg Nothing = id
showPkg (Just pkg) = ((pkg <> ":") <>)
showMth Nothing = id
showMth (Just (Method mt _ _)) = (<> (":" <> mt))
showSvc (Service sv _) = sv
showSvc (OneOf sv _) = sv
class ReflectRpcInfo (p :: Package') (s :: Service') (m :: Method') where
reflectRpcInfo :: Proxy p -> Proxy s -> Proxy m -> RequestHeaders -> i -> RpcInfo i
class ReflectService (s :: Service') where
reflectService :: Proxy s -> Service Text Text Text TyInfo
class ReflectMethod (m :: Method') where
reflectMethod :: Proxy m -> Method Text Text Text TyInfo
class ReflectArg (arg :: Argument') where
reflectArg :: Proxy arg -> Argument Text Text TyInfo
class ReflectReturn (r :: Return Symbol (TypeRef Symbol)) where
reflectReturn :: Proxy r -> Return Text TyInfo
class ReflectTyRef (r :: TypeRef Symbol) where
reflectTyRef :: Proxy r -> TyInfo
class KnownMaySymbol (m :: Maybe Symbol) where
maySymbolVal :: Proxy m -> Maybe Text
instance KnownMaySymbol 'Nothing where
maySymbolVal _ = Nothing
instance (KnownSymbol s) => KnownMaySymbol ('Just s) where
maySymbolVal _ = Just $ T.pack $ symbolVal (Proxy @s)
class KnownSymbols (m :: [Symbol]) where
symbolsVal :: Proxy m -> [Text]
instance KnownSymbols '[] where
symbolsVal _ = []
instance (KnownSymbol s, KnownSymbols ss) => KnownSymbols (s ': ss) where
symbolsVal _ = T.pack (symbolVal (Proxy @s)) : symbolsVal (Proxy @ss)
class ReflectServices (ss :: [Service']) where
reflectServices :: Proxy ss -> [Service Text Text Text TyInfo]
instance ReflectServices '[] where
reflectServices _ = []
instance (ReflectService s, ReflectServices ss)
=> ReflectServices (s ': ss) where
reflectServices _ = reflectService (Proxy @s) : reflectServices (Proxy @ss)
class ReflectMethods (ms :: [Method']) where
reflectMethods :: Proxy ms -> [Method Text Text Text TyInfo]
instance ReflectMethods '[] where
reflectMethods _ = []
instance (ReflectMethod m, ReflectMethods ms)
=> ReflectMethods (m ': ms) where
reflectMethods _ = reflectMethod (Proxy @m) : reflectMethods (Proxy @ms)
class ReflectArgs (ms :: [Argument']) where
reflectArgs :: Proxy ms -> [Argument Text Text TyInfo]
instance ReflectArgs '[] where
reflectArgs _ = []
instance (ReflectArg m, ReflectArgs ms)
=> ReflectArgs (m ': ms) where
reflectArgs _ = reflectArg (Proxy @m) : reflectArgs (Proxy @ms)
instance (KnownMaySymbol pname, ReflectServices ss, ReflectService s, ReflectMethod m)
=> ReflectRpcInfo ('Package pname ss) s m where
reflectRpcInfo _ ps pm req extra
= RpcInfo (Package (maySymbolVal (Proxy @pname))
(reflectServices (Proxy @ss)))
(reflectService ps) (Just (reflectMethod pm)) req extra
instance (KnownSymbol sname, ReflectMethods ms)
=> ReflectService ('Service sname ms) where
reflectService _
= Service (T.pack $ symbolVal (Proxy @sname))
(reflectMethods (Proxy @ms))
instance (KnownSymbol sname, KnownSymbols elts)
=> ReflectService ('OneOf sname elts) where
reflectService _
= OneOf (T.pack $ symbolVal (Proxy @sname))
(symbolsVal (Proxy @elts))
instance (KnownSymbol mname, ReflectArgs args, ReflectReturn r)
=> ReflectMethod ('Method mname args r) where
reflectMethod _
= Method (T.pack $ symbolVal (Proxy @mname))
(reflectArgs (Proxy @args)) (reflectReturn (Proxy @r))
instance (KnownMaySymbol aname, ReflectTyRef t)
=> ReflectArg ('ArgSingle aname t) where
reflectArg _
= ArgSingle (maySymbolVal (Proxy @aname)) (reflectTyRef (Proxy @t))
instance (KnownMaySymbol aname, ReflectTyRef t)
=> ReflectArg ('ArgStream aname t) where
reflectArg _
= ArgStream (maySymbolVal (Proxy @aname)) (reflectTyRef (Proxy @t))
instance ReflectReturn 'RetNothing where
reflectReturn _ = RetNothing
instance (ReflectTyRef t)
=> ReflectReturn ('RetSingle t) where
reflectReturn _ = RetSingle (reflectTyRef (Proxy @t))
instance (ReflectTyRef t)
=> ReflectReturn ('RetStream t) where
reflectReturn _ = RetStream (reflectTyRef (Proxy @t))
instance (ReflectTyRef e, ReflectTyRef t)
=> ReflectReturn ('RetThrows e t) where
reflectReturn _ = RetThrows (reflectTyRef (Proxy @e))
(reflectTyRef (Proxy @t))
instance ReflectTyRef t => ReflectTyRef ('ListRef t) where
reflectTyRef _ = TyList (reflectTyRef (Proxy @t))
instance ReflectTyRef t => ReflectTyRef ('OptionalRef t) where
reflectTyRef _ = TyOption (reflectTyRef (Proxy @t))
instance Typeable t => ReflectTyRef ('PrimitiveRef t) where
reflectTyRef _ = TyTy (T.pack $ show $ typeRep @t)
instance KnownSymbol s => ReflectTyRef ('ObjectRef s) where
reflectTyRef _ = TyTy (T.pack $ symbolVal $ Proxy @s)
instance KnownSymbol s => ReflectTyRef ('SchemaRef sch s) where
reflectTyRef _ = TyTy (T.pack $ symbolVal $ Proxy @s)
instance Typeable t => ReflectTyRef ('RegistryRef r t n) where
reflectTyRef _ = TyTy (T.pack $ show $ typeRep @t)