packages feed

mu-grpc-client-0.2.0.0: src/Mu/GRpc/Client/Record.hs

{-# language AllowAmbiguousTypes   #-}
{-# language DataKinds             #-}
{-# language FlexibleContexts      #-}
{-# language FlexibleInstances     #-}
{-# language MultiParamTypeClasses #-}
{-# language PolyKinds             #-}
{-# language ScopedTypeVariables   #-}
{-# language TemplateHaskell       #-}
{-# language TypeApplications      #-}
{-# language TypeFamilies          #-}
{-# language TypeOperators         #-}
{-# language UndecidableInstances  #-}
{-|
Description : Client for gRPC services using plain Haskell records

For further information over initialization of the connection,
consult the <http://hackage.haskell.org/package/http2-client-grpc http2-client-grpc docs>.
-}
module Mu.GRpc.Client.Record (
  -- * Initialization of the gRPC client
  GrpcClient
, GrpcClientConfig
, grpcClientConfigSimple
, setupGrpcClient'
  -- * Fill and generate the Haskell record of functions
, buildService
, GRpcMessageProtocol(..)
, CompressMode(..)
, GRpcReply(..)
, generateRecordFromService
) where

import           Control.Applicative
import           Data.Char
import           Data.Conduit                 (ConduitT)
import           Data.Proxy
import           Data.Void
import           GHC.Generics                 hiding (NoSourceStrictness, NoSourceUnpackedness)
import           GHC.TypeLits
import           Language.Haskell.TH          hiding (ppr)
import           Language.Haskell.TH.Datatype

import           Network.GRPC.Client          (CompressMode (..))
import           Network.GRPC.Client.Helpers

import           Mu.GRpc.Bridge
import           Mu.GRpc.Client.Internal
import           Mu.Rpc

-- | Fills in a Haskell record of functions with the corresponding
--   calls to gRPC services from a Mu 'Service' declaration.
buildService :: forall (pro :: GRpcMessageProtocol) (s :: Service') (p :: Symbol) t
                (nm :: Symbol) (anns :: [ServiceAnnotation]) (ms :: [Method Symbol]).
                (s ~ 'Service nm anns ms, Generic t, BuildService pro s p ms (Rep t))
             => GrpcClient -> t
buildService client = to (buildService' (Proxy @pro) (Proxy @s) (Proxy @p) (Proxy @ms) client)

class BuildService (pro :: GRpcMessageProtocol) (s :: Service')
                   (p :: Symbol) (ms :: [Method Symbol]) (f :: * -> *) where
  buildService' :: Proxy pro -> Proxy s -> Proxy p -> Proxy ms -> GrpcClient -> f a

instance BuildService pro s p ms U1 where
  buildService' _ _ _ _ _ = U1
instance BuildService pro s p ms f => BuildService pro s p ms (D1 meta f) where
  buildService' ppro ps ppr pms client
    = M1 (buildService' ppro ps ppr pms client)
instance BuildService pro s p ms f => BuildService pro s p ms (C1 meta f) where
  buildService' ppro ps ppr pms client
    = M1 (buildService' ppro ps ppr pms client)
instance TypeError ('Text "building a service from sums is not supported")
         => BuildService pro s p ms (f :+: g) where
  buildService' = error "this should never happen"
instance (BuildService pro s p ms f, BuildService pro s p ms g)
         => BuildService pro s p ms (f :*: g) where
  buildService' ppro ps ppr pms client
    = buildService' ppro ps ppr pms client :*: buildService' ppro ps ppr pms client
instance (m ~ AppendSymbol p x, GRpcServiceMethodCall pro s (s :-->: x) h)
         => BuildService pro s p ms (S1 ('MetaSel ('Just m) u ss ds) (K1 i h)) where
  buildService' ppro ps _ _ client
    = M1 $ K1 $ gRpcServiceMethodCall ppro ps (Proxy @(s :-->: x)) client

-- TEMPLATE HASKELL
-- ================

-- | Generate the plain Haskell record corresponding to
--   a Mu 'Service' definition, and a concrete implementation
--   of 'buildService' for that record.
generateRecordFromService :: String -> String -> Namer -> Name -> Q [Dec]
generateRecordFromService newRecordName fieldsPrefix tNamer serviceTyName
  = do let serviceTy = ConT serviceTyName
       srvDef <- typeToServiceDef serviceTy
       case srvDef of
         Nothing -> fail "service definition cannot be parsed"
         Just sd -> serviceDefToDecl serviceTyName newRecordName fieldsPrefix tNamer sd

type Namer = String -> String

serviceDefToDecl :: Name -> String -> String -> Namer -> Service String String -> Q [Dec]
serviceDefToDecl serviceTyName complete fieldsPrefix tNamer (Service _ _ methods)
  = do d <- dataD (pure [])
                  (mkName complete)
                  []
                  Nothing
                  [RecC (mkName complete) <$> mapM (methodToDecl fieldsPrefix tNamer) methods]
                  [pure (DerivClause Nothing [ConT ''Generic])]
       let buildName = mkName ("build" ++ complete)
       s <- SigD buildName <$> [t|GrpcClient -> $(return (ConT (mkName complete)))|]
       c <- Clause <$> pure []
                   <*> (NormalB <$> [e|buildService @($(return $ ConT serviceTyName))
                                                    @($(return $ LitT (StrTyLit fieldsPrefix)))|])
                   <*> pure []
       return [d, s, FunD buildName [c]]

methodToDecl :: String -> Namer -> Method String -> Q (Name, Bang, Type)
methodToDecl fieldsPrefix tNamer (Method mName _ args ret)
  = do let nm = firstLower (fieldsPrefix ++ mName)
       ty <- computeMethodType tNamer args ret
       return ( mkName nm, Bang NoSourceUnpackedness NoSourceStrictness, ty )

computeMethodType :: Namer -> [Argument] -> Return -> Q Type
computeMethodType _ [] RetNothing
  = [t|IO (GRpcReply ())|]
computeMethodType n [] (RetSingle r)
  = [t|IO (GRpcReply $(typeRefToType n r))|]
computeMethodType n [ArgSingle v] RetNothing
  = [t|$(typeRefToType n v) -> IO (GRpcReply ())|]
computeMethodType n [ArgSingle v] (RetSingle r)
  = [t|$(typeRefToType n v) -> IO (GRpcReply $(typeRefToType n r))|]
computeMethodType n [ArgStream v] (RetSingle r)
  = [t|CompressMode -> IO (ConduitT $(typeRefToType n v) Void IO (GRpcReply $(typeRefToType n r)))|]
computeMethodType n [ArgSingle v] (RetStream r)
  = [t|$(typeRefToType n v) -> IO (ConduitT () (GRpcReply $(typeRefToType n r)) IO ())|]
computeMethodType n [ArgStream v] (RetStream r)
  = [t|CompressMode -> IO (ConduitT $(typeRefToType n v) (GRpcReply $(typeRefToType n r)) IO ())|]
computeMethodType _ _ _ = fail "method signature not supported"

typeRefToType :: Namer -> TypeRef -> Q Type
typeRefToType tNamer (ViaTH (LitT (StrTyLit s)))
  = return $ ConT (mkName $ completeName tNamer s)
typeRefToType _tNamer (ViaTH ty)
  = return ty
typeRefToType _ _ = error "this should never happen"

completeName :: Namer -> String -> String
completeName namer name = firstUpper (namer (firstUpper name))

firstUpper :: String -> String
firstUpper []       = error "Empty names are not allowed"
firstUpper (x:rest) = toUpper x : rest

firstLower :: String -> String
firstLower []       = error "Empty names are not allowed"
firstLower (x:rest) = toLower x : rest

-- Parsing
-- =======

typeToServiceDef :: Type -> Q (Maybe (Service String String))
typeToServiceDef toplevelty
  = typeToServiceDef' <$> resolveTypeSynonyms toplevelty
  where
    typeToServiceDef' :: Type -> Maybe (Service String String)
    typeToServiceDef' expanded
      = do (sn, _, methods) <- tyD3 'Service expanded
           methods' <- tyList methods
           Service <$> tyString sn
                   <*> pure []
                   <*> mapM typeToMethodDef methods'

    typeToMethodDef :: Type -> Maybe (Method String)
    typeToMethodDef ty
      = do (mn, _, args, ret) <- tyD4 'Method ty
           args' <- tyList args
           Method <$> tyString mn
                  <*> pure []
                  <*> mapM typeToArgDef args'
                  <*> typeToRetDef ret

    typeToArgDef :: Type -> Maybe Argument
    typeToArgDef ty
      =   ArgSingle <$> (tyD1 'ArgSingle ty >>= typeToTypeRef)
      <|> ArgStream <$> (tyD1 'ArgStream ty >>= typeToTypeRef)

    typeToRetDef :: Type -> Maybe Return
    typeToRetDef ty
      =   RetNothing <$ tyD0 'RetNothing ty
      <|> RetSingle <$> (tyD1 'RetSingle ty >>= typeToTypeRef)
      <|> (do (e, v) <- tyD2 'RetThrows ty
              RetThrows <$> typeToTypeRef e <*> typeToTypeRef v)
      <|> RetStream <$> (tyD1 'RetStream ty >>= typeToTypeRef)

    typeToTypeRef :: Type -> Maybe TypeRef
    typeToTypeRef ty
      =   (do (_,innerTy) <- tyD2 'ViaSchema ty
              return (ViaTH innerTy))
      <|> (do (_,innerTy,_) <- tyD3 'ViaRegistry ty
              return (ViaTH innerTy))

tyString :: Type -> Maybe String
tyString (SigT t _)
  = tyString t
tyString (LitT (StrTyLit s))
  = Just s
tyString _
  = Nothing

tyList :: Type -> Maybe [Type]
tyList (SigT t _)
  = tyList t
tyList PromotedNilT
  = Just []
tyList (AppT (AppT PromotedConsT ty) rest)
  = (ty :) <$> tyList rest
tyList _ = Nothing

tyD0 :: Name -> Type -> Maybe ()
tyD0 name (SigT t _) = tyD0 name t
tyD0 name (PromotedT c)
  | c == name = Just ()
  | otherwise = Nothing
tyD0 _ _ = Nothing

tyD1 :: Name -> Type -> Maybe Type
tyD1 name (SigT t _) = tyD1 name t
tyD1 name (AppT (PromotedT c) x)
  | c == name = Just x
  | otherwise = Nothing
tyD1 _ _ = Nothing

tyD2 :: Name -> Type -> Maybe (Type, Type)
tyD2 name (SigT t _) = tyD2 name t
tyD2 name (AppT (AppT (PromotedT c) x) y)
  | c == name = Just (x, y)
  | otherwise = Nothing
tyD2 _ _ = Nothing

tyD3 :: Name -> Type -> Maybe (Type, Type, Type)
tyD3 name (SigT t _) = tyD3 name t
tyD3 name (AppT (AppT (AppT (PromotedT c) x) y) z)
  | c == name = Just (x, y, z)
  | otherwise = Nothing
tyD3 _ _ = Nothing

tyD4 :: Name -> Type -> Maybe (Type, Type, Type, Type)
tyD4 name (SigT t _) = tyD4 name t
tyD4 name (AppT (AppT (AppT (AppT (PromotedT c) x) y) z) u)
  | c == name = Just (x, y, z, u)
  | otherwise = Nothing
tyD4 _ _ = Nothing