├── project ├── build.properties ├── bintray.sbt ├── updateImpact.sbt └── protoc.sbt ├── .gitignore ├── .travis.yml ├── LICENSE ├── runtime └── src │ └── main │ └── scala │ └── grpcmonix │ └── GrpcMonix.scala ├── README.md └── generator └── src └── main └── scala └── grpcmonix └── generators └── GrpcMonixGenerator.scala /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.1.1 2 | 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | *.log 3 | target/ 4 | */target/ 5 | -------------------------------------------------------------------------------- /project/bintray.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("org.foundweekends" % "sbt-bintray" % "0.5.1") 2 | -------------------------------------------------------------------------------- /project/updateImpact.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.updateimpact" % "updateimpact-sbt-plugin" % "2.1.3") 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: trusty 2 | language: scala 3 | jdk: openjdk8 4 | 5 | script: 6 | - sbt clean package 7 | - test $TRAVIS_PULL_REQUEST = false && sbt updateImpactSubmit || true 8 | -------------------------------------------------------------------------------- /project/protoc.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.thesamet" % "sbt-protoc" % "0.99.19") 2 | 3 | libraryDependencies ++= Seq( 4 | "com.thesamet.scalapb" %% "compilerplugin" % "0.7.4" 5 | ) 6 | 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Beyond the lines 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /runtime/src/main/scala/grpcmonix/GrpcMonix.scala: -------------------------------------------------------------------------------- 1 | package grpcmonix 2 | 3 | import com.google.common.util.concurrent.ListenableFuture 4 | import io.grpc.stub.StreamObserver 5 | import monix.eval.{Callback, Task} 6 | import monix.execution.Ack.{Continue, Stop} 7 | import monix.execution.{Ack, Scheduler} 8 | import monix.reactive.Observable 9 | import monix.reactive.observables.ObservableLike.{Operator, Transformer} 10 | import monix.reactive.observers.Subscriber 11 | import monix.reactive.subjects.PublishSubject 12 | import org.reactivestreams.{Subscriber => SubscriberR} 13 | import scalapb.grpc.Grpc 14 | 15 | import scala.concurrent.Future 16 | 17 | object GrpcMonix { 18 | 19 | type GrpcOperator[I, O] = StreamObserver[O] => StreamObserver[I] 20 | 21 | def guavaFutureToMonixTask[T](future: ListenableFuture[T]): Task[T] = 22 | Task.deferFuture { 23 | Grpc.guavaFuture2ScalaFuture(future) 24 | } 25 | 26 | def grpcOperatorToMonixOperator[I,O](grpcOperator: GrpcOperator[I,O]): Operator[I,O] = { 27 | outputSubsriber: Subscriber[O] => 28 | val outputObserver: StreamObserver[O] = monixSubscriberToGrpcObserver(outputSubsriber) 29 | val inputObserver: StreamObserver[I] = grpcOperator(outputObserver) 30 | grpcObserverToMonixSubscriber(inputObserver, outputSubsriber.scheduler) 31 | } 32 | 33 | def monixSubscriberToGrpcObserver[T](subscriber: Subscriber[T]): StreamObserver[T] = 34 | new StreamObserver[T] { 35 | override def onError(t: Throwable): Unit = subscriber.onError(t) 36 | override def onCompleted(): Unit = subscriber.onComplete() 37 | override def onNext(value: T): Unit = subscriber.onNext(value) 38 | } 39 | 40 | def reactiveSubscriberToGrpcObserver[T](subscriber: SubscriberR[_ >: T]): StreamObserver[T] = 41 | new StreamObserver[T] { 42 | override def onError(t: Throwable): Unit = subscriber.onError(t) 43 | override def onCompleted(): Unit = subscriber.onComplete() 44 | override def onNext(value: T): Unit = subscriber.onNext(value) 45 | } 46 | 47 | def grpcObserverToMonixSubscriber[T](observer: StreamObserver[T], s: Scheduler): Subscriber[T] = 48 | new Subscriber[T] { 49 | override implicit def scheduler: Scheduler = s 50 | override def onError(t: Throwable): Unit = observer.onError(t) 51 | override def onComplete(): Unit = observer.onCompleted() 52 | override def onNext(value: T): Future[Ack] = 53 | try { 54 | observer.onNext(value) 55 | Continue 56 | } catch { 57 | case t: Throwable => 58 | observer.onError(t) 59 | Stop 60 | } 61 | } 62 | 63 | def grpcObserverToMonixCallback[T](observer: StreamObserver[T]): Callback[T] = 64 | new Callback[T] { 65 | override def onError(t: Throwable): Unit = observer.onError(t) 66 | override def onSuccess(value: T): Unit = { 67 | observer.onNext(value) 68 | observer.onCompleted() 69 | } 70 | } 71 | 72 | def liftByGrpcOperator[I, O](observable: Observable[I], operator: GrpcOperator[I, O]): Observable[O] = 73 | observable.liftByOperator( 74 | grpcOperatorToMonixOperator(operator) 75 | ) 76 | 77 | def unliftByTransformer[I, O](transformer: Transformer[I, O], subscriber: Subscriber[O]): Subscriber[I] = 78 | new Subscriber[I] { 79 | private[this] val subject = PublishSubject[I]() 80 | subject.transform(transformer).subscribe(subscriber) 81 | 82 | override implicit def scheduler: Scheduler = subscriber.scheduler 83 | override def onError(t: Throwable): Unit = subject.onError(t) 84 | override def onComplete(): Unit = subject.onComplete() 85 | override def onNext(value: I): Future[Ack] = subject.onNext(value) 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build status](https://api.travis-ci.org/btlines/grpcmonix.svg?branch=master)](https://travis-ci.org/btlines/grpcmonix) 2 | [![Dependencies](https://app.updateimpact.com/badge/852442212779298816/grpcmonix.svg?config=compile)](https://app.updateimpact.com/latest/852442212779298816/grpcmonix) 3 | [![License](https://img.shields.io/:license-MIT-blue.svg)](https://opensource.org/licenses/MIT) 4 | [![GRPCMonixGenerator](https://api.bintray.com/packages/beyondthelines/maven/grpcmonixgenerator/images/download.svg) ](https://bintray.com/beyondthelines/maven/grpcmonixgenerator/_latestVersion) 5 | [![GRPCMonixRuntime](https://api.bintray.com/packages/beyondthelines/maven/grpcmonixruntime/images/download.svg) ](https://bintray.com/beyondthelines/maven/grpcmonixruntime/_latestVersion) 6 | 7 | # GRPC Monix 8 | 9 | Use Monix's Tasks and Observables to implement your GRPC services instead of Java's StreamObservers. 10 | 11 | - Unary calls return a `Task[T]` for the response returned by the server 12 | - Server streaming calls return an `Observable[T]` for the elements returned by the server 13 | - Client streaming calls take an `Observable[T]` for the elements emitted by the client and return a `Task[U]` for the server response 14 | - Bidi streaming calls take an `Observable[T]` for the elements emitted by the client and return an `Observable[U]` for the elements returned by the server 15 | 16 | ## Installation 17 | 18 | You need to enable [`sbt-protoc`](https://github.com/thesamet/sbt-protoc) plugin to generate source code for the proto definitions. 19 | You can do it by adding a `protoc.sbt` file into your `project` folder with the following lines: 20 | 21 | ```scala 22 | addSbtPlugin("com.thesamet" % "sbt-protoc" % "0.99.17") 23 | 24 | resolvers += Resolver.bintrayRepo("beyondthelines", "maven") 25 | 26 | libraryDependencies ++= Seq( 27 | "com.thesamet.scalapb" %% "compilerplugin" % "0.7.0", 28 | "beyondthelines" %% "grpcmonixgenerator" % "0.0.7" 29 | ) 30 | ``` 31 | 32 | Here we add a dependency to the GRPCMonix protobuf generator. 33 | 34 | Then we need to trigger the generation from the `build.sbt`: 35 | 36 | ```scala 37 | PB.targets in Compile := Seq( 38 | // compile your proto files into scala source files 39 | scalapb.gen() -> (sourceManaged in Compile).value, 40 | // generate the GRPCMonix source code 41 | grpcmonix.generators.GrpcMonixGenerator() -> (sourceManaged in Compile).value 42 | ) 43 | 44 | resolvers += Resolver.bintrayRepo("beyondthelines", "maven") 45 | 46 | libraryDependencies += "beyondthelines" %% "grpcmonixruntime" % "0.0.7" 47 | ``` 48 | 49 | ### Usage 50 | 51 | You're now ready to implement your GRPC service using Monix's Tasks and Observables. 52 | 53 | To implement your service's business logic you simply extend the GRPC monix generated trait. 54 | 55 | E.g. for the RouteGuide service: 56 | 57 | ```scala 58 | class RouteGuideMonixService(features: Seq[Feature]) extends RouteGuideGrpcMonix.RouteGuide { 59 | // Unary call 60 | override def getFeature(request: Point): Task[Feature] = ??? 61 | // Server streaming 62 | override def listFeatures(request: Rectangle): Observable[Feature] = ??? 63 | // Client streaming 64 | override def recordRoute(points: Observable[Point]): Task[RouteSummary] = ??? 65 | // Bidi streaming 66 | override def routeChat(notes: Observable[RouteNote]): Observable[RouteNote] = ??? 67 | } 68 | ``` 69 | 70 | The server creation is similar except you need to provide a Monix's `Scheduler` instead of an `ExecutionContext` when binding the service 71 | 72 | ```scala 73 | val server = ServerBuilder 74 | .forPort(8980) 75 | .addService( 76 | RouteGuideGrpcMonix.bindService( 77 | new RouteGuideMonixService(features), // the service implemented above 78 | monix.execution.Scheduler.global 79 | ) 80 | ) 81 | .build() 82 | ``` 83 | 84 | Tasks and Observables are also available on the client side: 85 | 86 | ```scala 87 | val channel = ManagedChannelBuilder 88 | .forAddress("localhost", 8980) 89 | .usePlainText(true) 90 | .build() 91 | 92 | val stub = RouteGuideGrpcMonix.stub(channel) // only an async stub is provided 93 | 94 | // Unary call 95 | val feature: Task[Feature] = stub.getFeature(408031728, -748645385) 96 | // Server streaming 97 | val request = Rectangle( 98 | lo = Some(Point(408031728, -748645385)), 99 | hi = Some(Point(413700272, -742135189)) 100 | ) 101 | val features: Observable[Feature] = stub.listFeatures(request) 102 | // Client streaming 103 | val route: Observable[Feature] = Observable 104 | .fromIterable(features.map(_.getLocation)) 105 | .delayOnNext(100.millis) 106 | val summary: Task[RouteSummary] = stub.recordRoute(route) 107 | // Bidi streaming 108 | val notes: Observable[RouteNote] = Observable( 109 | RouteNote(message = "First message", location = Some(Point(0, 0))), 110 | RouteNote(message = "Second message", location = Some(Point(0, 1))), 111 | RouteNote(message = "Third message", location = Some(Point(1, 0))), 112 | RouteNote(message = "Fourth message", location = Some(Point(1, 1))) 113 | ).delayOnNext(1.second) 114 | val allNotes = stub.routeChat(notes) 115 | ``` 116 | -------------------------------------------------------------------------------- /generator/src/main/scala/grpcmonix/generators/GrpcMonixGenerator.scala: -------------------------------------------------------------------------------- 1 | package grpcmonix.generators 2 | 3 | import com.google.protobuf.Descriptors._ 4 | import com.google.protobuf.ExtensionRegistry 5 | import com.google.protobuf.compiler.PluginProtos.{CodeGeneratorRequest, CodeGeneratorResponse} 6 | import scalapb.compiler.FunctionalPrinter.PrinterEndo 7 | import scalapb.compiler.{DescriptorPimps, FunctionalPrinter, GeneratorParams, ProtobufGenerator, StreamType} 8 | import scalapb.options.compiler.Scalapb 9 | 10 | import scala.collection.JavaConverters._ 11 | 12 | 13 | object GrpcMonixGenerator { 14 | def apply(flatPackage: Boolean = false): GrpcMonixGenerator = { 15 | val params = GeneratorParams().copy(flatPackage = flatPackage) 16 | new GrpcMonixGenerator(params) 17 | } 18 | } 19 | 20 | class GrpcMonixGenerator(override val params: GeneratorParams) 21 | extends protocbridge.ProtocCodeGenerator with DescriptorPimps { 22 | def run(requestBytes: Array[Byte]): Array[Byte] = { 23 | // Read scalapb.options (if present) in .proto files 24 | val registry = ExtensionRegistry.newInstance() 25 | Scalapb.registerAllExtensions(registry) 26 | val b = CodeGeneratorResponse.newBuilder 27 | val request = CodeGeneratorRequest.parseFrom(requestBytes, registry) 28 | 29 | val fileDescByName: Map[String, FileDescriptor] = 30 | request.getProtoFileList.asScala.foldLeft[Map[String, FileDescriptor]](Map.empty) { 31 | case (acc, fp) => 32 | val deps = fp.getDependencyList.asScala.map(acc) 33 | acc + (fp.getName -> FileDescriptor.buildFrom(fp, deps.toArray)) 34 | } 35 | 36 | request.getFileToGenerateList.asScala.foreach { 37 | name => 38 | val fileDesc = fileDescByName(name) 39 | val responseFile = generateFile(fileDesc) 40 | b.addFile(responseFile) 41 | } 42 | b.build.toByteArray 43 | } 44 | 45 | private[this] def grpcObserver(typeName: String) = s"StreamObserver[$typeName]" 46 | 47 | private[this] def serviceCompanion(typeName: String) = s"ServiceCompanion[$typeName]" 48 | 49 | private[this] def task(typeParam: String) = s"Task[$typeParam]" 50 | 51 | private[this] def serviceMethodDescriptor(method: MethodDescriptor): PrinterEndo = { printer => 52 | val methodType = method.streamType match { 53 | case StreamType.Unary => "UNARY" 54 | case StreamType.ClientStreaming => "CLIENT_STREAMING" 55 | case StreamType.ServerStreaming => "SERVER_STREAMING" 56 | case StreamType.Bidirectional => "BIDI_STREAMING" 57 | } 58 | 59 | def marshaller(typeName: String) = s"new Marshaller($typeName)" 60 | 61 | printer 62 | .add(s"val ${method.descriptorName}: MethodDescriptor[${method.scalaIn}, ${method.scalaOut}] =") 63 | .indent 64 | .add("MethodDescriptor.newBuilder()") 65 | .addIndented( 66 | s".setType(MethodDescriptor.MethodType.$methodType)", 67 | s""".setFullMethodName(MethodDescriptor.generateFullMethodName("${method.getService.getFullName}", "${method.getName}"))""", 68 | s".setRequestMarshaller(new Marshaller(${method.scalaIn}))", 69 | s".setResponseMarshaller(new Marshaller(${method.scalaOut}))", 70 | ".build()" 71 | ) 72 | .outdent 73 | } 74 | 75 | private[this] def serviceDescriptor(service: ServiceDescriptor): PrinterEndo = 76 | _.add(s"""val SERVICE: _root_.io.grpc.ServiceDescriptor = _root_.io.grpc.ServiceDescriptor.newBuilder("${service.getFullName}")""") 77 | .indent 78 | .add(s".setSchemaDescriptor(new ConcreteProtoFileDescriptorSupplier(${service.getFile.fileDescriptorObjectFullName}.javaDescriptor))") 79 | .print(service.methods) { case (printer, method) => 80 | printer.add(s".addMethod(${method.descriptorName})") 81 | } 82 | .add(".build()") 83 | .outdent 84 | 85 | private[this] def serviceMethodSignature(method: MethodDescriptor) = { 86 | s"def ${method.name}" + (method.streamType match { 87 | case StreamType.Unary => 88 | s"(request: ${method.scalaIn}): ${task(method.scalaOut)}" 89 | case StreamType.ClientStreaming => 90 | s"(input: Observable[${method.scalaIn}]): ${task(method.scalaOut)}" 91 | case StreamType.ServerStreaming => 92 | s"(request: ${method.scalaIn}): Observable[${method.scalaOut}]" 93 | case StreamType.Bidirectional => 94 | s"(input: Observable[${method.scalaIn}]): Observable[${method.scalaOut}]" 95 | }) 96 | } 97 | 98 | private[this] def serviceTrait(service: ServiceDescriptor): PrinterEndo = { printer => 99 | printer 100 | .add(s"trait ${service.getName} extends AbstractService {") 101 | .indent 102 | .add(s"override def serviceCompanion = ${service.getName}") 103 | .seq(service.methods.map(serviceMethodSignature)) 104 | .outdent 105 | .add("}") 106 | } 107 | 108 | private[this] def serviceTraitCompanion(service: ServiceDescriptor, fileDesc: FileDescriptor): PrinterEndo = { printer => 109 | printer 110 | .add(s"object ${service.getName} extends ${serviceCompanion(service.getName)} {") 111 | .indent 112 | .add(s"implicit def serviceCompanion: ${serviceCompanion(service.getName)} = this") 113 | .add(s"def javaDescriptor: ServiceDescriptor = ${fileDesc.fileDescriptorObjectFullName}.javaDescriptor.getServices().get(0)") 114 | .outdent 115 | .add("}") 116 | } 117 | 118 | private[this] def stub(service: ServiceDescriptor): PrinterEndo = { printer => 119 | printer 120 | .add(s"class ${service.stub}(") 121 | .indent 122 | .add(s"channel: Channel,") 123 | .add(s"options: CallOptions = CallOptions.DEFAULT") 124 | .outdent 125 | .add(s") extends AbstractStub[${service.stub}](channel, options) with ${service.name} {") 126 | .indent 127 | .print(service.getMethods.asScala) { 128 | case (p, m) => p.call(clientMethodImpl(m)) 129 | } 130 | .add(s"override def build(channel: Channel, options: CallOptions): ${service.stub} = ") 131 | .indent 132 | .add(s"new ${service.stub}(channel, options)") 133 | .outdent 134 | .outdent 135 | .add("}") 136 | } 137 | 138 | private[this] def clientMethodImpl(method: MethodDescriptor): PrinterEndo = { printer => 139 | def liftByGrpcOperator(inputType: String, outputType: String) = s"liftByGrpcOperator[$inputType, $outputType]" 140 | 141 | method.streamType match { 142 | case StreamType.Unary => 143 | printer 144 | .add(s"override ${serviceMethodSignature(method)} = ") 145 | .indent 146 | .add("guavaFutureToMonixTask(") 147 | .indent 148 | .add(s"ClientCalls.futureUnaryCall(channel.newCall(${method.descriptorName}, options), request)") 149 | .outdent 150 | .add(")") 151 | .outdent 152 | case StreamType.ClientStreaming => 153 | printer 154 | .add(s"override ${serviceMethodSignature(method)} = ") 155 | .indent 156 | .add(s"${liftByGrpcOperator(method.scalaIn, method.scalaOut)}(") 157 | .indent 158 | .add("input,") 159 | .add(s"outputObserver =>") 160 | .indent 161 | .add("ClientCalls.asyncClientStreamingCall(") 162 | .indent 163 | .add(s"channel.newCall(${method.descriptorName}, options),") 164 | .add("outputObserver") 165 | .outdent 166 | .add(")") 167 | .outdent 168 | .outdent 169 | .add(").firstL") 170 | .outdent 171 | case StreamType.ServerStreaming => 172 | printer 173 | .add(s"override ${serviceMethodSignature(method)} = ") 174 | .indent 175 | .add(s"Observable.fromReactivePublisher(new PublisherR[${method.scalaOut}] {") 176 | .indent 177 | .add(s"override def subscribe(subscriber: SubscriberR[_ >: ${method.scalaOut}]): Unit = {") 178 | .indent 179 | .add("ClientCalls.asyncServerStreamingCall(") 180 | .addIndented( 181 | s"channel.newCall(${method.descriptorName}, options),", 182 | "request,", 183 | s"reactiveSubscriberToGrpcObserver[${method.scalaOut}](subscriber)" 184 | ) 185 | .add(")") 186 | .outdent 187 | .add("}") 188 | .outdent 189 | .add("})") 190 | .outdent 191 | case StreamType.Bidirectional => 192 | printer 193 | .add(s"override ${serviceMethodSignature(method)} = ") 194 | .indent 195 | .add(s"${liftByGrpcOperator(method.scalaIn, method.scalaOut)}(") 196 | .indent 197 | .add("input,") 198 | .add("outputObserver =>") 199 | .indent 200 | .add("ClientCalls.asyncBidiStreamingCall(") 201 | .indent 202 | .add(s"channel.newCall(${method.descriptorName}, options),") 203 | .add("outputObserver") 204 | .outdent 205 | .add(")") 206 | .outdent 207 | .outdent 208 | .add(")") 209 | .outdent 210 | } 211 | } 212 | 213 | private[this] def bindService(service: ServiceDescriptor): PrinterEndo = { printer => 214 | printer 215 | .add(s"def bindService(serviceImpl: ${service.name}, scheduler: Scheduler): ServerServiceDefinition = ") 216 | .indent 217 | .add("ServerServiceDefinition") 218 | .indent 219 | .add(".builder(SERVICE)") 220 | .print(service.methods) { case (p, m) => 221 | p.call(addMethodImplementation(m)) 222 | } 223 | .add(".build()") 224 | .outdent 225 | .outdent 226 | } 227 | 228 | private[this] def addMethodImplementation(method: MethodDescriptor): PrinterEndo = { printer => 229 | def unliftByTransformer(inputType: String, outputType: String) = s"unliftByTransformer[$inputType, $outputType]" 230 | 231 | val call = method.streamType match { 232 | case StreamType.Unary => "ServerCalls.asyncUnaryCall" 233 | case StreamType.ClientStreaming => "ServerCalls.asyncClientStreamingCall" 234 | case StreamType.ServerStreaming => "ServerCalls.asyncServerStreamingCall" 235 | case StreamType.Bidirectional => "ServerCalls.asyncBidiStreamingCall" 236 | } 237 | val serverMethod = method.streamType match { 238 | case StreamType.Unary => s"ServerCalls.UnaryMethod[${method.scalaIn}, ${method.scalaOut}]" 239 | case StreamType.ClientStreaming => s"ServerCalls.ClientStreamingMethod[${method.scalaIn}, ${method.scalaOut}]" 240 | case StreamType.ServerStreaming => s"ServerCalls.ServerStreamingMethod[${method.scalaIn}, ${method.scalaOut}]" 241 | case StreamType.Bidirectional => s"ServerCalls.BidiStreamingMethod[${method.scalaIn}, ${method.scalaOut}]" 242 | } 243 | val impl: PrinterEndo = method.streamType match { 244 | case StreamType.Unary => 245 | _ 246 | .add(s"override def invoke(request: ${method.scalaIn}, observer: ${grpcObserver(method.scalaOut)}): Unit =") 247 | .indent 248 | .add(s"serviceImpl.${method.name}(request).runAsync(grpcObserverToMonixCallback(observer))(scheduler)") 249 | .outdent 250 | case StreamType.ClientStreaming => 251 | _ 252 | .add(s"override def invoke(observer: ${grpcObserver(method.scalaOut)}): ${grpcObserver(method.scalaIn)} = {") 253 | .indent 254 | .add("val outputSubscriber = grpcObserverToMonixSubscriber(observer, scheduler)") 255 | .add(s"val inputSubscriber = ${unliftByTransformer(method.scalaIn, method.scalaOut)}(") 256 | .indent 257 | .add(s"inputObservable => Observable.fromTask(serviceImpl.${method.name}(inputObservable)),") 258 | .add("outputSubscriber") 259 | .outdent 260 | .add(")") 261 | .add("monixSubscriberToGrpcObserver(inputSubscriber)") 262 | .outdent 263 | .add("}") 264 | case StreamType.ServerStreaming => 265 | _ 266 | .add(s"override def invoke(request: ${method.scalaIn}, observer: ${grpcObserver(method.scalaOut)}): Unit = ") 267 | .indent 268 | .add(s"serviceImpl.${method.name}(request).subscribe(grpcObserverToMonixSubscriber(observer, scheduler))") 269 | .outdent 270 | case StreamType.Bidirectional => 271 | _ 272 | .add(s"override def invoke(observer: ${grpcObserver(method.scalaOut)}): ${grpcObserver(method.scalaIn)} = {") 273 | .indent 274 | .add("val outputSubscriber = grpcObserverToMonixSubscriber(observer, scheduler)") 275 | .add(s"val inputSubscriber = ${unliftByTransformer(method.scalaIn, method.scalaOut)}(") 276 | .indent 277 | .add(s"inputObservable => serviceImpl.${method.name}(inputObservable),") 278 | .add("outputSubscriber") 279 | .outdent 280 | .add(")") 281 | .add("monixSubscriberToGrpcObserver(inputSubscriber)") 282 | .outdent 283 | .add("}") 284 | } 285 | 286 | printer 287 | .add(".addMethod(") 288 | .indent 289 | .add(s"${method.descriptorName},") 290 | .add(s"$call(") 291 | .indent 292 | .add(s"new $serverMethod {") 293 | .indent 294 | .call(impl) 295 | .outdent 296 | .add("}") 297 | .outdent 298 | .add(")") 299 | .outdent 300 | .add(")") 301 | } 302 | 303 | private[this] def javaDescriptor(service: ServiceDescriptor): PrinterEndo = { printer => 304 | printer 305 | .add(s"def javaDescriptor: ServiceDescriptor = ") 306 | .indent 307 | .add(s"${service.getFile.fileDescriptorObjectFullName}.javaDescriptor.getServices().get(${service.getIndex})") 308 | .outdent 309 | } 310 | 311 | def generateFile(fileDesc: FileDescriptor): CodeGeneratorResponse.File = { 312 | val b = CodeGeneratorResponse.File.newBuilder() 313 | 314 | val objectName = fileDesc 315 | .fileDescriptorObjectName 316 | .substring(0, fileDesc.fileDescriptorObjectName.length - 5) + "GrpcMonix" 317 | 318 | b.setName(s"${fileDesc.scalaDirectory}/$objectName.scala") 319 | val fp = FunctionalPrinter() 320 | .add(s"package ${fileDesc.scalaPackageName}") 321 | .newline 322 | .add("import _root_.com.google.protobuf.Descriptors.ServiceDescriptor") 323 | .add("import _root_.scalapb.grpc.{ AbstractService, ConcreteProtoFileDescriptorSupplier, Marshaller, ServiceCompanion }") 324 | .add("import _root_.io.grpc.{ CallOptions, Channel, MethodDescriptor, ServerServiceDefinition }") 325 | .add("import _root_.grpcmonix.GrpcMonix._") 326 | .add("import _root_.io.grpc.stub.{ AbstractStub, ClientCalls, ServerCalls, StreamObserver }") 327 | .add("import _root_.monix.eval.Task") 328 | .add("import _root_.monix.execution.{ Cancelable, Scheduler }") 329 | .add("import _root_.monix.reactive.Observable") 330 | .add("import _root_.org.reactivestreams.{ Publisher => PublisherR, Subscriber => SubscriberR }") 331 | .newline 332 | .add(s"object $objectName {") 333 | .indent 334 | .newline 335 | .print(fileDesc.getServices.asScala) { 336 | case (printer, service) => 337 | printer 338 | .print(service.getMethods.asScala) { 339 | case (p, m) => p.call(serviceMethodDescriptor(m)) 340 | } 341 | .newline 342 | .call(serviceDescriptor(service)) 343 | .newline 344 | .call(serviceTrait(service)) 345 | .newline 346 | .call(serviceTraitCompanion(service, fileDesc)) 347 | .newline 348 | .call(stub(service)) 349 | .newline 350 | .call(bindService(service)) 351 | .newline 352 | .add(s"def stub(channel: Channel): ${service.stub} = new ${service.stub}(channel)") 353 | .newline 354 | .call(javaDescriptor(service)) 355 | } 356 | .outdent 357 | .add("}") 358 | .newline 359 | 360 | b.setContent(fp.result) 361 | b.build 362 | } 363 | } 364 | --------------------------------------------------------------------------------