-- Copyright (c) Microsoft. All rights reserved. -- Licensed under the MIT license. See LICENSE file in the project root for full license information. {-# LANGUAGE OverloadedStrings, QuasiQuotes, RecordWildCards #-} module Language.Bond.Codegen.Cs.Grpc_cs ( grpc_cs) where import Data.Monoid import qualified Data.Text.Lazy as L import qualified Language.Bond.Codegen.Cs.Util as CS import Language.Bond.Codegen.TypeMapping import Language.Bond.Codegen.Util import Language.Bond.Syntax.Types hiding (methodTypeToMaybe) import Language.Bond.Util import Prelude import Text.Shakespeare.Text -- | Represents a parameter in an abstract server method. -- -- This data definition would be in the where clause of grpc_cs if data -- definitions were permitted in where clauses. data CsServerMethodParam = CsServerMethodParam { name :: L.Text, csType :: L.Text } -- | Generates a C# parameter declaration for a server method parameter. csServerParamDecl :: CsServerMethodParam -> L.Text csServerParamDecl (CsServerMethodParam {..}) = [lt|#{csType} #{name}|] -- | Contains splicable text for a client method's first parameter. Create -- one of these with the function csClientRequestParam. Some kinds of -- methods don't need a first param, so some of the record's fields may be -- empty. If non-empty, the correct comma and space are included so this -- text can just be spliced in. -- -- This data definition would be in the where clause of grpc_cs if data -- definitions were permitted in where clauses. data CsClientRequestParam = CsClientRequestParam { bareParam :: L.Text -- bare param declaration: what the caller passes in, if applicable , messageParam :: L.Text -- param declaration wrapped in an IMessage , toMessage :: L.Text -- code to create an IMessage for the request , forwardMessageParam :: L.Text -- code to forward the IMessage param on to the CallInvoker } -- | Codegen template for generating code containing declarations of -- of services including proxies and services files for gRPC integration. grpc_cs :: MappingContext -> String -> [Import] -> [Declaration] -> (String, L.Text) grpc_cs cs _ _ declarations = ("_grpc.cs", [lt| #{CS.disableCscWarnings} #{CS.disableReSharperWarnings} namespace #{csNamespace} { using System.Collections.Generic; #{doubleLineSep 1 grpc declarations} } // #{csNamespace} |]) where csNamespace = sepBy "." toText $ getNamespace cs idl = MappingContext idlTypeMapping [] [] [] grpc s@Service{..} = [lt|#{CS.typeAttributes cs s}public static class #{declName}#{generics} #{genericsWhere} { static readonly string ServiceName = "#{getDeclTypeName idl s}"; #{doubleLineSep 2 methodDeclaration serviceMethods} public abstract class #{declName}Base { #{doubleLineSep 3 serverBaseMethods serviceMethods} } public class #{declName}Client : global::Grpc.Core.ClientBase<#{declName}Client> { public #{declName}Client(global::Grpc.Core.Channel channel) : base(channel) { } protected #{declName}Client() : base() { } protected #{declName}Client(global::Grpc.Core.ClientBase.ClientBaseConfiguration configuration) : base(configuration) { } #{doubleLineSep 3 clientMethods serviceMethods} protected override #{declName}Client NewInstance(global::Grpc.Core.ClientBase.ClientBaseConfiguration configuration) { return new #{declName}Client(configuration); } } public static global::Grpc.Core.ServerServiceDefinition BindService(#{declName}Base serviceImpl) { return global::Grpc.Core.ServerServiceDefinition.CreateBuilder() #{newlineSep 5 serviceMethodBuilder serviceMethods} .Build(); } } |] where methodNames = map methodName serviceMethods uniqImplName name = uniqueName (name ++ "_impl") methodNames csTypeOf Void = "global::Bond.Void" csTypeOf (Unary t) = getTypeName cs t csTypeOf (Streaming t) = getTypeName cs t csRequestTypeName (Function {..}) = csTypeOf methodInput csRequestTypeName (Event {..}) = csTypeOf methodInput csResponseTypeName (Function {..}) = csTypeOf methodResult csResponseTypeName Event{} = csTypeOf Void messageVoid = [lt|global::Bond.Grpc.IMessage|] messageOf :: Type -> L.Text messageOf t = [lt|global::Bond.Grpc.IMessage<#{getTypeName cs t}>|] methodTypeEnum :: Method -> L.Text methodTypeEnum (Function {..}) = case (methodResult, methodInput) of (Streaming _, Streaming _) -> [lt|global::Grpc.Core.MethodType.DuplexStreaming|] (Streaming _, _) -> [lt|global::Grpc.Core.MethodType.ServerStreaming|] (_, Streaming _) -> [lt|global::Grpc.Core.MethodType.ClientStreaming|] _ -> [lt|global::Grpc.Core.MethodType.Unary|] methodTypeEnum (Event {..}) = case methodInput of Streaming _ -> error ("Unexpected event '" ++ methodName ++ "' with Streaming parameter") -- the parser should have rejected this _ -> [lt|global::Grpc.Core.MethodType.Unary|] generics = angles $ sepBy ", " paramName declParams genericsWhere = sepBy " " addWhere declParams where addWhere a = [lt|where #{paramName a} : class|] methodDeclaration m = [lt|static readonly global::Grpc.Core.Method, global::Bond.Grpc.IMessage<#{csResponseTypeName m}>> Method_#{methodName m} = new global::Grpc.Core.Method, global::Bond.Grpc.IMessage<#{csResponseTypeName m}>>( #{methodTypeEnum m}, ServiceName, "#{methodName m}", global::Bond.Grpc.Marshaller<#{csRequestTypeName m}>.Instance, global::Bond.Grpc.Marshaller<#{csResponseTypeName m}>.Instance);|] serverMethodParams :: MethodType -> MethodType -> [CsServerMethodParam] serverMethodParams reqType respType = (forRequest reqType) ++ (forResponse respType) ++ context where forRequest Void = [(CsServerMethodParam [lt|request|] messageVoid)] forRequest (Unary t) = [(CsServerMethodParam [lt|request|] [lt|#{messageOf t}|])] forRequest (Streaming t) = [(CsServerMethodParam [lt|requests|] [lt|Grpc.Core.IAsyncStreamReader<#{messageOf t}>|])] forResponse Void = [] forResponse (Unary _) = [] forResponse (Streaming t) = [(CsServerMethodParam [lt|responses|] [lt|Grpc.Core.IAsyncStreamWriter<#{messageOf t}>|])] context = [(CsServerMethodParam [lt|context|] [lt|global::Grpc.Core.ServerCallContext|])] -- | For functions (not events), given the result type, compute the -- C# abstract method return type. csServerFunctionReturn :: MethodType -> L.Text csServerFunctionReturn Void = [lt|global::System.Threading.Tasks.Task<#{messageVoid}>|] csServerFunctionReturn (Unary t) = [lt|global::System.Threading.Tasks.Task<#{messageOf t}>|] csServerFunctionReturn (Streaming _) = [lt|global::System.Threading.Tasks.Task|] serverBaseMethods Function{..} = [lt|#{CS.schemaAttributes 3 methodAttributes}public abstract #{csServerFunctionReturn methodResult} #{methodName}(#{csParams});|] where params = serverMethodParams methodInput methodResult csParams = commaSep csServerParamDecl params serverBaseMethods Event{..} = [lt|#{CS.schemaAttributes 3 methodAttributes}public abstract global::System.Threading.Tasks.Task #{methodName}(#{csParams}); internal global::System.Threading.Tasks.Task<#{messageVoid}> #{uniqImplName methodName}(#{csParams}) { return global::Bond.Grpc.Internal.NothingCallHandler.Handle(#{handlerArgs}); }|] where params = serverMethodParams methodInput Void csParams = commaSep csServerParamDecl params handlerArgs = commaSep id ((L.pack methodName):(map name params)) -- | For functions (not events), given the input and result types, -- compute the C# client method return type. csClientFunctionReturn :: MethodType -> MethodType -> L.Text csClientFunctionReturn (Streaming input) (Streaming result) = [lt|global::Grpc.Core.AsyncDuplexStreamingCall<#{messageOf input}, #{messageOf result}>|] csClientFunctionReturn (Streaming input) (Unary result) = [lt|global::Grpc.Core.AsyncClientStreamingCall<#{messageOf input}, #{messageOf result}>|] csClientFunctionReturn _ (Streaming result) = [lt|global::Grpc.Core.AsyncServerStreamingCall<#{messageOf result}>|] csClientFunctionReturn _ (Unary result) = [lt|global::Grpc.Core.AsyncUnaryCall<#{messageOf result}>|] csClientFunctionReturn _ Void = [lt|global::Grpc.Core.AsyncUnaryCall<#{messageVoid}>|] -- | Given the input type, compute the client method parameter. csClientRequestParam :: MethodType -> CsClientRequestParam csClientRequestParam Void = CsClientRequestParam { bareParam = mempty , messageParam = [lt|#{messageVoid} request, |] , toMessage = [lt|global::Bond.Grpc.Message.Void, |] , forwardMessageParam = [lt|, request|] } csClientRequestParam (Unary t) = CsClientRequestParam { bareParam = [lt|#{getTypeName cs t} request, |] , messageParam = [lt|#{messageOf t} request, |] , toMessage = [lt|global::Bond.Grpc.Message.From(request), |] , forwardMessageParam = [lt|, request|] } csClientRequestParam (Streaming _) = CsClientRequestParam { bareParam = mempty , messageParam = mempty , toMessage = mempty , forwardMessageParam = mempty } -- | Given the input and result types, compute the call invoker needed. clientCallInvoker :: MethodType -> MethodType -> L.Text clientCallInvoker (Streaming _) (Streaming _) = [lt|AsyncDuplexStreamingCall|] clientCallInvoker (Streaming _) (Unary _) = [lt|AsyncClientStreamingCall|] clientCallInvoker (Unary _) (Streaming _) = [lt|AsyncServerStreamingCall|] clientCallInvoker _ _ = [lt|AsyncUnaryCall|] clientMethods Function{..} = [lt|#{CS.schemaAttributes 3 methodAttributes}public virtual #{csClientFunctionReturn methodInput methodResult} #{methodName}Async(#{bareParam requestParam}global::Grpc.Core.Metadata headers = null, global::System.DateTime? deadline = null, global::System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken)) { return #{methodName}Async(#{toMessage requestParam}new global::Grpc.Core.CallOptions(headers, deadline, cancellationToken)); } #{CS.schemaAttributes 3 methodAttributes}public virtual #{csClientFunctionReturn methodInput methodResult} #{methodName}Async(#{messageParam requestParam}global::Grpc.Core.CallOptions options) { return CallInvoker.#{clientCallInvoker methodInput methodResult}(Method_#{methodName}, null, options#{forwardMessageParam requestParam}); }|] where requestParam = csClientRequestParam methodInput clientMethods Event{..} = [lt|#{CS.schemaAttributes 3 methodAttributes}public virtual void #{methodName}Async(#{bareParam requestParam}global::Grpc.Core.Metadata headers = null, global::System.DateTime? deadline = null, global::System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken)) { #{methodName}Async(#{toMessage requestParam}new global::Grpc.Core.CallOptions(headers, deadline, cancellationToken)); } #{CS.schemaAttributes 3 methodAttributes}public virtual void #{methodName}Async(#{messageParam requestParam}global::Grpc.Core.CallOptions options) { global::Bond.Grpc.Internal.NothingCallInvoker.NothingCall(CallInvoker, Method_#{methodName}, null, options#{forwardMessageParam requestParam}); }|] where requestParam = csClientRequestParam methodInput serviceMethodBuilder Function{..} = [lt|.AddMethod(Method_#{methodName}, serviceImpl.#{methodName})|] serviceMethodBuilder Event{..} = [lt|.AddMethod(Method_#{methodName}, serviceImpl.#{uniqImplName methodName})|] grpc _ = mempty