Skip to content

Commit 98143c2

Browse files
Merge pull request #810 from iRevive/topic/ctx-as-implicit-param
Allow generating context as an implicit parameter
2 parents f0a3a31 + 4875fd9 commit 98143c2

File tree

15 files changed

+643
-20
lines changed

15 files changed

+643
-20
lines changed

build.sbt

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,14 +161,27 @@ lazy val e2e = (projectMatrix in file("e2e"))
161161
val output = (Compile / sourceManaged).value / "fs2-grpc" / "disable-trailers"
162162
}
163163

164+
val renderContextAsImplicit = new {
165+
val args = Seq(
166+
"fs2_grpc:service_suffix=Fs2GrpcRenderContextAsImplicit",
167+
"fs2_grpc:render_context_as_implicit"
168+
) ++ (if (tlIsScala3.value) Seq("scala3_sources") else Nil)
169+
170+
val output = (Compile / sourceManaged).value / "fs2-grpc" / "render-context-as-implicit"
171+
}
172+
164173
Seq(
165174
scalapb.gen() -> (Compile / sourceManaged).value / "scalapb",
166175
genModule(codegenFullName + "$") -> (Compile / sourceManaged).value / "fs2-grpc",
167-
(genModule(codegenFullName + "$"), disableTrailers.args) -> disableTrailers.output
176+
(genModule(codegenFullName + "$"), disableTrailers.args) -> disableTrailers.output,
177+
(genModule(codegenFullName + "$"), renderContextAsImplicit.args) -> renderContextAsImplicit.output
168178
)
169179
},
170180
buildInfoPackage := "fs2.grpc.e2e.buildinfo",
171-
buildInfoKeys := Seq[BuildInfoKey]("sourceManaged" -> (Compile / sourceManaged).value / "fs2-grpc"),
181+
buildInfoKeys := Seq[BuildInfoKey](
182+
"sourceManaged" -> (Compile / sourceManaged).value / "fs2-grpc",
183+
"scalaVersion" -> scalaVersion.value
184+
),
172185
githubWorkflowArtifactUpload := false,
173186
scalacOptions := {
174187
if (tlIsScala3.value) {

codegen/src/main/scala/fs2/grpc/codegen/Fs2AbstractServicePrinter.scala

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ abstract class Fs2AbstractServicePrinter extends Fs2ServicePrinter {
3232
val service: ServiceDescriptor
3333
val serviceSuffix: String
3434
val di: DescriptorImplicits
35+
protected[this] val renderContextAsImplicit: Boolean = false
36+
protected[this] val scala3Sources: Boolean = false
3537

3638
import di._
3739

@@ -55,6 +57,18 @@ abstract class Fs2AbstractServicePrinter extends Fs2ServicePrinter {
5557
basicClientCall
5658
}
5759

60+
protected[this] def renderCtxParameter(): String = {
61+
if (renderContextAsImplicit) {
62+
if (scala3Sources) {
63+
s")(using ctx: $Ctx"
64+
} else {
65+
s")(implicit ctx: $Ctx"
66+
}
67+
} else {
68+
s", ctx: $Ctx"
69+
}
70+
}
71+
5872
private[this] def serviceMethodImplementation(method: MethodDescriptor): PrinterEndo = { p =>
5973
val inType = method.inputType.scalaType
6074
val outType = method.outputType.scalaType
@@ -79,6 +93,15 @@ abstract class Fs2AbstractServicePrinter extends Fs2ServicePrinter {
7993
val handler = s"$Fs2ServerCallHandler[G](dispatcher, serverOptions).${handleMethod(method)}[$inType, $outType]"
8094

8195
val serviceCall = s"serviceImpl.${method.name}"
96+
val invoke = if (renderContextAsImplicit) {
97+
if (scala3Sources) {
98+
s"$serviceCall(r)(using m)"
99+
} else {
100+
s"$serviceCall(r)(m)"
101+
}
102+
} else {
103+
s"$serviceCall(r, m)"
104+
}
82105

83106
p.addStringMargin {
84107
s"""|.addMethod(
@@ -87,7 +110,7 @@ abstract class Fs2AbstractServicePrinter extends Fs2ServicePrinter {
87110
| serviceAspect.${visitMethod(method)}[$inType, $outType](
88111
| ${ServiceCallContext}(m, $descriptor),
89112
| r,
90-
| (r, m) => $serviceCall(r, m)
113+
| (r, m) => $invoke
91114
| )
92115
| }
93116
|)"""

codegen/src/main/scala/fs2/grpc/codegen/Fs2CodeGenerator.scala

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,28 +33,42 @@ import scala.jdk.CollectionConverters.*
3333
sealed trait Fs2Params {
3434
def serviceSuffix: String
3535
def disableTrailers: Boolean
36+
def renderContextAsImplicit: Boolean
37+
def scala3Sources: Boolean
3638

3739
def withServiceSuffix(serviceSuffix: String): Fs2Params
3840
def withDisableTrailers(value: Boolean): Fs2Params
41+
def withRenderContextAsImplicit(value: Boolean): Fs2Params
42+
def withScala3Sources(value: Boolean): Fs2Params
3943
}
4044

4145
object Fs2Params {
4246

4347
def default: Fs2Params =
4448
Impl(
4549
serviceSuffix = "Fs2Grpc",
46-
disableTrailers = false
50+
disableTrailers = false,
51+
renderContextAsImplicit = false,
52+
scala3Sources = false
4753
)
4854

4955
private final case class Impl(
5056
serviceSuffix: String,
51-
disableTrailers: Boolean
57+
disableTrailers: Boolean,
58+
renderContextAsImplicit: Boolean,
59+
scala3Sources: Boolean
5260
) extends Fs2Params {
5361
def withServiceSuffix(serviceSuffix: String): Fs2Params =
5462
copy(serviceSuffix = serviceSuffix)
5563

5664
def withDisableTrailers(value: Boolean): Fs2Params =
5765
copy(disableTrailers = value)
66+
67+
def withRenderContextAsImplicit(value: Boolean): Fs2Params =
68+
copy(renderContextAsImplicit = value)
69+
70+
def withScala3Sources(value: Boolean): Fs2Params =
71+
copy(scala3Sources = value)
5872
}
5973
}
6074

@@ -92,7 +106,13 @@ object Fs2CodeGenerator extends CodeGenApp {
92106
service,
93107
fs2params.serviceSuffix + "Trailers",
94108
di,
95-
new Fs2GrpcExhaustiveTrailersServicePrinter(_, fs2params.serviceSuffix + "Trailers", di)
109+
new Fs2GrpcExhaustiveTrailersServicePrinter(
110+
_,
111+
fs2params.serviceSuffix + "Trailers",
112+
fs2params.renderContextAsImplicit,
113+
fs2params.scala3Sources,
114+
di
115+
)
96116
)
97117
)
98118

@@ -102,7 +122,13 @@ object Fs2CodeGenerator extends CodeGenApp {
102122
service,
103123
fs2params.serviceSuffix,
104124
di,
105-
new Fs2GrpcServicePrinter(_, fs2params.serviceSuffix, di)
125+
new Fs2GrpcServicePrinter(
126+
_,
127+
fs2params.serviceSuffix,
128+
fs2params.renderContextAsImplicit,
129+
fs2params.scala3Sources,
130+
di
131+
)
106132
)
107133

108134
trailers :+ general
@@ -114,9 +140,11 @@ object Fs2CodeGenerator extends CodeGenApp {
114140
paramsAndUnparsed <- GeneratorParams.fromStringCollectUnrecognized(params)
115141
params = paramsAndUnparsed._1
116142
unparsed = paramsAndUnparsed._2
117-
suffix <- unparsed.map(_.split("=", 2).toList).foldLeft[Either[String, Fs2Params]](Right(Fs2Params.default)) {
143+
initial = Fs2Params.default.withScala3Sources(params.scala3Sources)
144+
suffix <- unparsed.map(_.split("=", 2).toList).foldLeft[Either[String, Fs2Params]](Right(initial)) {
118145
case (Right(params), ServiceSuffix :: suffix :: Nil) => Right(params.withServiceSuffix(suffix))
119146
case (Right(params), DisableTrailers :: Nil) => Right(params.withDisableTrailers(true))
147+
case (Right(params), RenderContextAsImplicit :: Nil) => Right(params.withRenderContextAsImplicit(true))
120148
case (Right(_), ServiceSuffixPluginKey :: _ :: Nil) =>
121149
Left(s"The '$ServiceSuffixPluginKey' is replaced with '$ServiceSuffix'")
122150
case (Right(_), xs) => Left(s"Unrecognized parameter: $xs")
@@ -152,4 +180,5 @@ object Fs2CodeGenerator extends CodeGenApp {
152180
private[codegen] val ServiceSuffixPluginKey: String = "serviceSuffix"
153181
private[codegen] val ServiceSuffix: String = "fs2_grpc:service_suffix"
154182
private[codegen] val DisableTrailers: String = "fs2_grpc:disable_trailers"
183+
private[codegen] val RenderContextAsImplicit: String = "fs2_grpc:render_context_as_implicit"
155184
}

codegen/src/main/scala/fs2/grpc/codegen/Fs2GrpcExhaustiveTrailersServicePrinter.scala

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ import scalapb.compiler.{DescriptorImplicits, StreamType}
2727
class Fs2GrpcExhaustiveTrailersServicePrinter(
2828
val service: ServiceDescriptor,
2929
val serviceSuffix: String,
30+
override val renderContextAsImplicit: Boolean,
31+
override val scala3Sources: Boolean,
3032
val di: DescriptorImplicits
3133
) extends Fs2AbstractServicePrinter {
3234
import fs2.grpc.codegen.Fs2AbstractServicePrinter.constants._
@@ -36,13 +38,13 @@ class Fs2GrpcExhaustiveTrailersServicePrinter(
3638

3739
val scalaInType = method.inputType.scalaType
3840
val scalaOutType = method.outputType.scalaType
39-
val ctx = s"ctx: $Ctx"
41+
val ctx = renderCtxParameter()
4042

4143
s"def ${method.name}" + (method.streamType match {
42-
case StreamType.Unary => s"(request: $scalaInType, $ctx): F[($scalaOutType, $Metadata)]"
43-
case StreamType.ClientStreaming => s"(request: $Stream[F, $scalaInType], $ctx): F[($scalaOutType, $Metadata)]"
44-
case StreamType.ServerStreaming => s"(request: $scalaInType, $ctx): $Stream[F, $scalaOutType]"
45-
case StreamType.Bidirectional => s"(request: $Stream[F, $scalaInType], $ctx): $Stream[F, $scalaOutType]"
44+
case StreamType.Unary => s"(request: $scalaInType$ctx): F[($scalaOutType, $Metadata)]"
45+
case StreamType.ClientStreaming => s"(request: $Stream[F, $scalaInType]$ctx): F[($scalaOutType, $Metadata)]"
46+
case StreamType.ServerStreaming => s"(request: $scalaInType$ctx): $Stream[F, $scalaOutType]"
47+
case StreamType.Bidirectional => s"(request: $Stream[F, $scalaInType]$ctx): $Stream[F, $scalaOutType]"
4648
})
4749
}
4850

codegen/src/main/scala/fs2/grpc/codegen/Fs2GrpcServicePrinter.scala

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,22 +24,27 @@ package fs2.grpc.codegen
2424
import com.google.protobuf.Descriptors.{MethodDescriptor, ServiceDescriptor}
2525
import scalapb.compiler.{DescriptorImplicits, StreamType}
2626

27-
class Fs2GrpcServicePrinter(val service: ServiceDescriptor, val serviceSuffix: String, val di: DescriptorImplicits)
28-
extends Fs2AbstractServicePrinter {
27+
class Fs2GrpcServicePrinter(
28+
val service: ServiceDescriptor,
29+
val serviceSuffix: String,
30+
override val renderContextAsImplicit: Boolean,
31+
override val scala3Sources: Boolean,
32+
val di: DescriptorImplicits
33+
) extends Fs2AbstractServicePrinter {
2934
import fs2.grpc.codegen.Fs2AbstractServicePrinter.constants._
3035
import di._
3136

3237
override protected def serviceMethodSignature(method: MethodDescriptor): String = {
3338

3439
val scalaInType = method.inputType.scalaType
3540
val scalaOutType = method.outputType.scalaType
36-
val ctx = s"ctx: $Ctx"
41+
val ctx = renderCtxParameter()
3742

3843
s"def ${method.name}" + (method.streamType match {
39-
case StreamType.Unary => s"(request: $scalaInType, $ctx): F[$scalaOutType]"
40-
case StreamType.ClientStreaming => s"(request: $Stream[F, $scalaInType], $ctx): F[$scalaOutType]"
41-
case StreamType.ServerStreaming => s"(request: $scalaInType, $ctx): $Stream[F, $scalaOutType]"
42-
case StreamType.Bidirectional => s"(request: $Stream[F, $scalaInType], $ctx): $Stream[F, $scalaOutType]"
44+
case StreamType.Unary => s"(request: $scalaInType$ctx): F[$scalaOutType]"
45+
case StreamType.ClientStreaming => s"(request: $Stream[F, $scalaInType]$ctx): F[$scalaOutType]"
46+
case StreamType.ServerStreaming => s"(request: $scalaInType$ctx): $Stream[F, $scalaOutType]"
47+
case StreamType.Bidirectional => s"(request: $Stream[F, $scalaInType]$ctx): $Stream[F, $scalaOutType]"
4348
})
4449
}
4550

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
package hello.world
2+
3+
import _root_.cats.syntax.all._
4+
5+
/** TestService: Example gRPC service used in e2e tests
6+
* It demonstrates all four RPC shapes.
7+
*/
8+
trait TestServiceFs2GrpcRenderContextAsImplicit[F[_], A] {
9+
/** Unary RPC: no streaming in either direction
10+
*/
11+
def noStreaming(request: hello.world.TestMessage)(implicit ctx: A): F[hello.world.TestMessage]
12+
/** Client streaming RPC: client streams, server returns a single response
13+
*/
14+
def clientStreaming(request: _root_.fs2.Stream[F, hello.world.TestMessage])(implicit ctx: A): F[hello.world.TestMessage]
15+
/** Server streaming RPC: client sends one request, server streams responses
16+
*/
17+
def serverStreaming(request: hello.world.TestMessage)(implicit ctx: A): _root_.fs2.Stream[F, hello.world.TestMessage]
18+
/** Bidirectional streaming RPC: both client and server stream
19+
*/
20+
def bothStreaming(request: _root_.fs2.Stream[F, hello.world.TestMessage])(implicit ctx: A): _root_.fs2.Stream[F, hello.world.TestMessage]
21+
}
22+
23+
object TestServiceFs2GrpcRenderContextAsImplicit extends _root_.fs2.grpc.GeneratedCompanion[TestServiceFs2GrpcRenderContextAsImplicit] {
24+
25+
def serviceDescriptor: _root_.io.grpc.ServiceDescriptor = hello.world.TestServiceGrpc.SERVICE
26+
27+
def mkClientFull[F[_], G[_]: _root_.cats.effect.Async, A](
28+
dispatcher: _root_.cats.effect.std.Dispatcher[G],
29+
channel: _root_.io.grpc.Channel,
30+
clientAspect: _root_.fs2.grpc.client.ClientAspect[F, G, A],
31+
clientOptions: _root_.fs2.grpc.client.ClientOptions
32+
): TestServiceFs2GrpcRenderContextAsImplicit[F, A] = new TestServiceFs2GrpcRenderContextAsImplicit[F, A] {
33+
def noStreaming(request: hello.world.TestMessage)(implicit ctx: A): F[hello.world.TestMessage] =
34+
clientAspect.visitUnaryToUnaryCall[hello.world.TestMessage, hello.world.TestMessage](
35+
_root_.fs2.grpc.client.ClientCallContext(ctx, hello.world.TestServiceGrpc.METHOD_NO_STREAMING),
36+
request,
37+
(req, m) => _root_.fs2.grpc.client.Fs2ClientCall[G](channel, hello.world.TestServiceGrpc.METHOD_NO_STREAMING, dispatcher, clientOptions).flatMap(_.unaryToUnaryCall(req, m))
38+
)
39+
def clientStreaming(request: _root_.fs2.Stream[F, hello.world.TestMessage])(implicit ctx: A): F[hello.world.TestMessage] =
40+
clientAspect.visitStreamingToUnaryCall[hello.world.TestMessage, hello.world.TestMessage](
41+
_root_.fs2.grpc.client.ClientCallContext(ctx, hello.world.TestServiceGrpc.METHOD_CLIENT_STREAMING),
42+
request,
43+
(req, m) => _root_.fs2.grpc.client.Fs2ClientCall[G](channel, hello.world.TestServiceGrpc.METHOD_CLIENT_STREAMING, dispatcher, clientOptions).flatMap(_.streamingToUnaryCall(req, m))
44+
)
45+
def serverStreaming(request: hello.world.TestMessage)(implicit ctx: A): _root_.fs2.Stream[F, hello.world.TestMessage] =
46+
clientAspect.visitUnaryToStreamingCall[hello.world.TestMessage, hello.world.TestMessage](
47+
_root_.fs2.grpc.client.ClientCallContext(ctx, hello.world.TestServiceGrpc.METHOD_SERVER_STREAMING),
48+
request,
49+
(req, m) => _root_.fs2.Stream.eval(_root_.fs2.grpc.client.Fs2ClientCall[G](channel, hello.world.TestServiceGrpc.METHOD_SERVER_STREAMING, dispatcher, clientOptions)).flatMap(_.unaryToStreamingCall(req, m))
50+
)
51+
def bothStreaming(request: _root_.fs2.Stream[F, hello.world.TestMessage])(implicit ctx: A): _root_.fs2.Stream[F, hello.world.TestMessage] =
52+
clientAspect.visitStreamingToStreamingCall[hello.world.TestMessage, hello.world.TestMessage](
53+
_root_.fs2.grpc.client.ClientCallContext(ctx, hello.world.TestServiceGrpc.METHOD_BOTH_STREAMING),
54+
request,
55+
(req, m) => _root_.fs2.Stream.eval(_root_.fs2.grpc.client.Fs2ClientCall[G](channel, hello.world.TestServiceGrpc.METHOD_BOTH_STREAMING, dispatcher, clientOptions)).flatMap(_.streamingToStreamingCall(req, m))
56+
)
57+
}
58+
59+
protected def serviceBindingFull[F[_], G[_]: _root_.cats.effect.Async, A](
60+
dispatcher: _root_.cats.effect.std.Dispatcher[G],
61+
serviceImpl: TestServiceFs2GrpcRenderContextAsImplicit[F, A],
62+
serviceAspect: _root_.fs2.grpc.server.ServiceAspect[F, G, A],
63+
serverOptions: _root_.fs2.grpc.server.ServerOptions
64+
) = {
65+
_root_.io.grpc.ServerServiceDefinition
66+
.builder(hello.world.TestServiceGrpc.SERVICE)
67+
.addMethod(
68+
hello.world.TestServiceGrpc.METHOD_NO_STREAMING,
69+
_root_.fs2.grpc.server.Fs2ServerCallHandler[G](dispatcher, serverOptions).unaryToUnaryCall[hello.world.TestMessage, hello.world.TestMessage]{ (r, m) =>
70+
serviceAspect.visitUnaryToUnaryCall[hello.world.TestMessage, hello.world.TestMessage](
71+
_root_.fs2.grpc.server.ServiceCallContext(m, hello.world.TestServiceGrpc.METHOD_NO_STREAMING),
72+
r,
73+
(r, m) => serviceImpl.noStreaming(r)(m)
74+
)
75+
}
76+
)
77+
.addMethod(
78+
hello.world.TestServiceGrpc.METHOD_CLIENT_STREAMING,
79+
_root_.fs2.grpc.server.Fs2ServerCallHandler[G](dispatcher, serverOptions).streamingToUnaryCall[hello.world.TestMessage, hello.world.TestMessage]{ (r, m) =>
80+
serviceAspect.visitStreamingToUnaryCall[hello.world.TestMessage, hello.world.TestMessage](
81+
_root_.fs2.grpc.server.ServiceCallContext(m, hello.world.TestServiceGrpc.METHOD_CLIENT_STREAMING),
82+
r,
83+
(r, m) => serviceImpl.clientStreaming(r)(m)
84+
)
85+
}
86+
)
87+
.addMethod(
88+
hello.world.TestServiceGrpc.METHOD_SERVER_STREAMING,
89+
_root_.fs2.grpc.server.Fs2ServerCallHandler[G](dispatcher, serverOptions).unaryToStreamingCall[hello.world.TestMessage, hello.world.TestMessage]{ (r, m) =>
90+
serviceAspect.visitUnaryToStreamingCall[hello.world.TestMessage, hello.world.TestMessage](
91+
_root_.fs2.grpc.server.ServiceCallContext(m, hello.world.TestServiceGrpc.METHOD_SERVER_STREAMING),
92+
r,
93+
(r, m) => serviceImpl.serverStreaming(r)(m)
94+
)
95+
}
96+
)
97+
.addMethod(
98+
hello.world.TestServiceGrpc.METHOD_BOTH_STREAMING,
99+
_root_.fs2.grpc.server.Fs2ServerCallHandler[G](dispatcher, serverOptions).streamingToStreamingCall[hello.world.TestMessage, hello.world.TestMessage]{ (r, m) =>
100+
serviceAspect.visitStreamingToStreamingCall[hello.world.TestMessage, hello.world.TestMessage](
101+
_root_.fs2.grpc.server.ServiceCallContext(m, hello.world.TestServiceGrpc.METHOD_BOTH_STREAMING),
102+
r,
103+
(r, m) => serviceImpl.bothStreaming(r)(m)
104+
)
105+
}
106+
)
107+
.build()
108+
}
109+
110+
}

0 commit comments

Comments
 (0)