Skip to content

Commit 1aa5323

Browse files
Merge pull request #809 from iRevive/topic/disable-trailers
Allow disabling trailers
2 parents 320869f + 1818791 commit 1aa5323

File tree

5 files changed

+228
-16
lines changed

5 files changed

+228
-16
lines changed

build.sbt

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ inThisBuild(
1818
List(
1919
githubWorkflowBuildSbtStepPreamble := Seq(),
2020
scalaVersion := Scala3,
21-
tlBaseVersion := "2.11",
21+
tlBaseVersion := "3.0",
2222
startYear := Some(2018),
2323
licenses := Seq(("MIT", url("https://github.com/typelevel/fs2-grpc/blob/master/LICENSE"))),
2424
organizationName := "Gary Coady / Fs2 Grpc Developers",
@@ -146,10 +146,18 @@ lazy val e2e = (projectMatrix in file("e2e"))
146146
ceMunit % Test,
147147
"io.grpc" % "grpc-inprocess" % versions.grpc % Test
148148
),
149-
Compile / PB.targets := Seq(
150-
scalapb.gen() -> (Compile / sourceManaged).value / "scalapb",
151-
genModule(codegenFullName + "$") -> (Compile / sourceManaged).value / "fs2-grpc"
152-
),
149+
Compile / PB.targets := {
150+
val disableTrailers = new {
151+
val args = Seq("serviceSuffix=Fs2GrpcDisableTrailers", "fs2_grpc:disable_trailers")
152+
val output = (Compile / sourceManaged).value / "fs2-grpc" / "disable-trailers"
153+
}
154+
155+
Seq(
156+
scalapb.gen() -> (Compile / sourceManaged).value / "scalapb",
157+
genModule(codegenFullName + "$") -> (Compile / sourceManaged).value / "fs2-grpc",
158+
(genModule(codegenFullName + "$"), disableTrailers.args) -> disableTrailers.output
159+
)
160+
},
153161
buildInfoPackage := "fs2.grpc.e2e.buildinfo",
154162
buildInfoKeys := Seq[BuildInfoKey]("sourceManaged" -> (Compile / sourceManaged).value / "fs2-grpc"),
155163
githubWorkflowArtifactUpload := false,

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

Lines changed: 49 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,33 @@ import scalapb.options.Scalapb
3030

3131
import scala.jdk.CollectionConverters.*
3232

33-
final case class Fs2Params(serviceSuffix: String = "Fs2Grpc")
33+
sealed trait Fs2Params {
34+
def serviceSuffix: String
35+
def disableTrailers: Boolean
36+
37+
def withServiceSuffix(serviceSuffix: String): Fs2Params
38+
def withDisableTrailers(value: Boolean): Fs2Params
39+
}
40+
41+
object Fs2Params {
42+
43+
def default: Fs2Params =
44+
Impl(
45+
serviceSuffix = "Fs2Grpc",
46+
disableTrailers = false
47+
)
48+
49+
private final case class Impl(
50+
serviceSuffix: String,
51+
disableTrailers: Boolean
52+
) extends Fs2Params {
53+
def withServiceSuffix(serviceSuffix: String): Fs2Params =
54+
copy(serviceSuffix = serviceSuffix)
55+
56+
def withDisableTrailers(value: Boolean): Fs2Params =
57+
copy(disableTrailers = value)
58+
}
59+
}
3460

3561
object Fs2CodeGenerator extends CodeGenApp {
3662

@@ -56,20 +82,30 @@ object Fs2CodeGenerator extends CodeGenApp {
5682
di: DescriptorImplicits
5783
): Seq[PluginProtos.CodeGeneratorResponse.File] = {
5884
file.getServices.asScala.flatMap { service =>
59-
generateServiceFile(
60-
file,
61-
service,
62-
fs2params.serviceSuffix + "Trailers",
63-
di,
64-
new Fs2GrpcExhaustiveTrailersServicePrinter(_, fs2params.serviceSuffix + "Trailers", di)
65-
) ::
85+
val trailers =
86+
if (fs2params.disableTrailers)
87+
Nil
88+
else
89+
List(
90+
generateServiceFile(
91+
file,
92+
service,
93+
fs2params.serviceSuffix + "Trailers",
94+
di,
95+
new Fs2GrpcExhaustiveTrailersServicePrinter(_, fs2params.serviceSuffix + "Trailers", di)
96+
)
97+
)
98+
99+
val general =
66100
generateServiceFile(
67101
file,
68102
service,
69103
fs2params.serviceSuffix,
70104
di,
71105
new Fs2GrpcServicePrinter(_, fs2params.serviceSuffix, di)
72-
) :: Nil
106+
)
107+
108+
trailers :+ general
73109
}.toSeq
74110
}
75111

@@ -78,8 +114,9 @@ object Fs2CodeGenerator extends CodeGenApp {
78114
paramsAndUnparsed <- GeneratorParams.fromStringCollectUnrecognized(params)
79115
params = paramsAndUnparsed._1
80116
unparsed = paramsAndUnparsed._2
81-
suffix <- unparsed.map(_.split("=", 2).toList).foldLeft[Either[String, Fs2Params]](Right(Fs2Params())) {
82-
case (Right(params), ServiceSuffix :: suffix :: Nil) => Right(params.copy(serviceSuffix = suffix))
117+
suffix <- unparsed.map(_.split("=", 2).toList).foldLeft[Either[String, Fs2Params]](Right(Fs2Params.default)) {
118+
case (Right(params), ServiceSuffix :: suffix :: Nil) => Right(params.withServiceSuffix(suffix))
119+
case (Right(params), DisableTrailers :: Nil) => Right(params.withDisableTrailers(true))
83120
case (Right(_), xs) => Left(s"Unrecognized parameter: $xs")
84121
case (Left(e), _) => Left(e)
85122
}
@@ -111,4 +148,5 @@ object Fs2CodeGenerator extends CodeGenApp {
111148
}
112149

113150
private[codegen] val ServiceSuffix: String = "serviceSuffix"
151+
private[codegen] val DisableTrailers: String = "fs2_grpc:disable_trailers"
114152
}
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 TestServiceFs2GrpcDisableTrailers[F[_], A] {
9+
/** Unary RPC: no streaming in either direction
10+
*/
11+
def noStreaming(request: hello.world.TestMessage, 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], 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, 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], ctx: A): _root_.fs2.Stream[F, hello.world.TestMessage]
21+
}
22+
23+
object TestServiceFs2GrpcDisableTrailers extends _root_.fs2.grpc.GeneratedCompanion[TestServiceFs2GrpcDisableTrailers] {
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+
): TestServiceFs2GrpcDisableTrailers[F, A] = new TestServiceFs2GrpcDisableTrailers[F, A] {
33+
def noStreaming(request: hello.world.TestMessage, 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], 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, 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], 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: TestServiceFs2GrpcDisableTrailers[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+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Copyright (c) 2018 Gary Coady / Fs2 Grpc Developers
3+
*
4+
* Permission is hereby granted, free of charge, to any person obtaining a copy of
5+
* this software and associated documentation files (the "Software"), to deal in
6+
* the Software without restriction, including without limitation the rights to
7+
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
8+
* the Software, and to permit persons to whom the Software is furnished to do so,
9+
* subject to the following conditions:
10+
*
11+
* The above copyright notice and this permission notice shall be included in all
12+
* copies or substantial portions of the Software.
13+
*
14+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
16+
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
17+
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
18+
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19+
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20+
*/
21+
22+
package fs2.grpc.e2e
23+
24+
import java.io.File
25+
26+
import fs2.grpc.GeneratedCompanion
27+
import fs2.grpc.e2e.buildinfo.BuildInfo.sourceManaged
28+
import hello.world._
29+
30+
import scala.io.Source
31+
32+
class Fs2CodeGeneratorDisableTrailersSpec extends munit.FunSuite {
33+
34+
val sourcesGenerated = new File(sourceManaged.getAbsolutePath, "disable-trailers/hello/world")
35+
36+
test("code generator outputs correct service file") {
37+
val testFileName = "TestServiceFs2GrpcDisableTrailers.scala"
38+
val reference = Source.fromResource(s"$testFileName.txt").getLines().mkString("\n")
39+
val generated = Source.fromFile(new File(sourcesGenerated, testFileName)).getLines().mkString("\n")
40+
41+
assertEquals(generated, reference)
42+
}
43+
44+
test("code generator outputs: do not generate trailers") {
45+
assertEquals(sourcesGenerated.list().length, 1)
46+
}
47+
48+
test("implicit of companion resolves") {
49+
implicitly[GeneratedCompanion[TestServiceFs2GrpcDisableTrailers]]
50+
}
51+
52+
}

plugin/src/main/scala/fs2/grpc/codegen/Fs2GrpcPlugin.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@ object Fs2GrpcPlugin extends AutoPlugin {
7070
case object Scala3Sources extends CodeGeneratorOption {
7171
override def toString: String = "scala3_sources"
7272
}
73+
// Disable generation of the trailers
74+
case object Fs2GrpcDisableTrailers extends CodeGeneratorOption {
75+
override def toString: String = "fs2_grpc:disable_trailers"
76+
}
7377
}
7478

7579
val scalapbCodeGeneratorOptions =

0 commit comments

Comments
 (0)