Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add service binding for mkCtx returning Resource #567

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,18 @@ class Fs2GrpcServicePrinter(service: ServiceDescriptor, serviceSuffix: String, d
p.add(s".addMethod($descriptor, $handler((r, m) => $eval.flatMap($serviceCall(r, _))))")
}

private[this] def serviceBindingResourceImplementation(method: MethodDescriptor): PrinterEndo = { p =>
val inType = method.inputType.scalaType
val outType = method.outputType.scalaType
val descriptor = method.grpcDescriptor.fullName
val handler = s"$Fs2ServerCallHandler[F](dispatcher, serverOptions).${handleMethod(method)}[$inType, $outType]"

val serviceCall = s"serviceImpl.${method.name}"
val eval = if (method.isServerStreaming) s"$Stream.resource[F, A](mkCtx(m)).flatMap" else "mkCtx(m).use"
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is really the crux of the PR. I'd like to cut down on the code duplication I just added to this file, but wanted to get general feedback on the PR before spending any time on that.


p.add(s".addMethod($descriptor, $handler((r, m) => $eval($serviceCall(r, _))))")
}

private[this] def serviceMethods: PrinterEndo = _.seq(service.methods.map(serviceMethodSignature))

private[this] def serviceMethodImplementations: PrinterEndo =
Expand All @@ -103,6 +115,13 @@ class Fs2GrpcServicePrinter(service: ServiceDescriptor, serviceSuffix: String, d
.add(".build()")
.outdent

private[this] def serviceBindingResourceImplementations: PrinterEndo =
_.indent
.add(s".builder(${service.grpcDescriptor.fullName})")
.call(service.methods.map(serviceBindingResourceImplementation): _*)
.add(".build()")
.outdent

private[this] def serviceTrait: PrinterEndo =
_.add(s"trait $serviceNameFs2[F[_], $Ctx] {").indent.call(serviceMethods).outdent.add("}")

Expand All @@ -111,6 +130,8 @@ class Fs2GrpcServicePrinter(service: ServiceDescriptor, serviceSuffix: String, d
.call(serviceClient)
.newline
.call(serviceBinding)
.newline
.call(serviceBindingResource)
.outdent
.newline
.add("}")
Expand All @@ -134,6 +155,16 @@ class Fs2GrpcServicePrinter(service: ServiceDescriptor, serviceSuffix: String, d
.add("}")
}

private[this] def serviceBindingResource: PrinterEndo = {
_.add(
s"protected def serviceBindingResource[F[_]: $Async, $Ctx](dispatcher: $Dispatcher[F], serviceImpl: $serviceNameFs2[F, $Ctx], mkCtx: $Metadata => $Resource[F, $Ctx], serverOptions: $ServerOptions): $ServerServiceDefinition = {"
).indent
.add(s"$ServerServiceDefinition")
.call(serviceBindingResourceImplementations)
.outdent
.add("}")
}

// /

def printService(printer: FunctionalPrinter): FunctionalPrinter = {
Expand Down
10 changes: 10 additions & 0 deletions e2e/src/test/resources/TestServiceFs2Grpc.scala.txt
Original file line number Diff line number Diff line change
Expand Up @@ -43,5 +43,15 @@ object TestServiceFs2Grpc extends _root_.fs2.grpc.GeneratedCompanion[TestService
.addMethod(hello.world.TestServiceGrpc.METHOD_BOTH_STREAMING, _root_.fs2.grpc.server.Fs2ServerCallHandler[F](dispatcher, serverOptions).streamingToStreamingCall[hello.world.TestMessage, hello.world.TestMessage]((r, m) => _root_.fs2.Stream.eval(mkCtx(m)).flatMap(serviceImpl.bothStreaming(r, _))))
.build()
}

protected def serviceBindingResource[F[_]: _root_.cats.effect.Async, A](dispatcher: _root_.cats.effect.std.Dispatcher[F], serviceImpl: TestServiceFs2Grpc[F, A], mkCtx: _root_.io.grpc.Metadata => _root_.cats.effect.Resource[F, A], serverOptions: _root_.fs2.grpc.server.ServerOptions): _root_.io.grpc.ServerServiceDefinition = {
_root_.io.grpc.ServerServiceDefinition
.builder(hello.world.TestServiceGrpc.SERVICE)
.addMethod(hello.world.TestServiceGrpc.METHOD_NO_STREAMING, _root_.fs2.grpc.server.Fs2ServerCallHandler[F](dispatcher, serverOptions).unaryToUnaryCall[hello.world.TestMessage, hello.world.TestMessage]((r, m) => mkCtx(m).use(serviceImpl.noStreaming(r, _))))
.addMethod(hello.world.TestServiceGrpc.METHOD_CLIENT_STREAMING, _root_.fs2.grpc.server.Fs2ServerCallHandler[F](dispatcher, serverOptions).streamingToUnaryCall[hello.world.TestMessage, hello.world.TestMessage]((r, m) => mkCtx(m).use(serviceImpl.clientStreaming(r, _))))
.addMethod(hello.world.TestServiceGrpc.METHOD_SERVER_STREAMING, _root_.fs2.grpc.server.Fs2ServerCallHandler[F](dispatcher, serverOptions).unaryToStreamingCall[hello.world.TestMessage, hello.world.TestMessage]((r, m) => _root_.fs2.Stream.resource[F, A](mkCtx(m)).flatMap(serviceImpl.serverStreaming(r, _))))
.addMethod(hello.world.TestServiceGrpc.METHOD_BOTH_STREAMING, _root_.fs2.grpc.server.Fs2ServerCallHandler[F](dispatcher, serverOptions).streamingToStreamingCall[hello.world.TestMessage, hello.world.TestMessage]((r, m) => _root_.fs2.Stream.resource[F, A](mkCtx(m)).flatMap(serviceImpl.bothStreaming(r, _))))
.build()
}

}
14 changes: 14 additions & 0 deletions runtime/src/main/scala/fs2/grpc/GeneratedCompanion.scala
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,13 @@ trait GeneratedCompanion[Service[*[_], _]] {
serverOptions: ServerOptions
): ServerServiceDefinition

protected def serviceBindingResource[F[_]: Async, A](
dispatcher: Dispatcher[F],
serviceImpl: Service[F, A],
mkCtx: Metadata => Resource[F, A],
serverOptions: ServerOptions
): ServerServiceDefinition

final def service[F[_]: Async, A](
dispatcher: Dispatcher[F],
serviceImpl: Service[F, A],
Expand All @@ -139,6 +146,13 @@ trait GeneratedCompanion[Service[*[_], _]] {
serviceBinding[F, A](dispatcher, serviceImpl, mkCtx, serverOptions)
}

final def service[F[_]: Async, A](
serviceImpl: Service[F, A],
f: Metadata => Resource[F, A],
serverOptions: ServerOptions
): Resource[F, ServerServiceDefinition] =
Dispatcher[F].map(serviceBindingResource[F, A](_, serviceImpl, f, serverOptions))
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't able to add the error handling that the above method has due to the different signature.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about something like:

  final def service[F[_]: Async, A](
      dispatcher: Dispatcher[F],
      serviceImpl: Service[F, A],
      f: Metadata => F[A],
      serverOptions: ServerOptions
  ): ServerServiceDefinition = {

    val mkCtx: Metadata => F[A] = f(_).handleErrorWith {
      case e: StatusException => e.raiseError[F, A]
      case e: StatusRuntimeException => e.raiseError[F, A]
      case e: Throwable => Status.INTERNAL.withDescription(e.getMessage).asRuntimeException().raiseError[F, A]
    }

    val lifted: Metadata => Resource[F, A] = m => Resource.eval(mkCtx(m))

    serviceBindingResource[F, A](dispatcher, serviceImpl, lifted, serverOptions)
  }

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As we are on 2.5.0-RC1 we can make a breaking change with respect to this method:

  protected def serviceBinding[F[_]: Async, A](
      dispatcher: Dispatcher[F],
      serviceImpl: Service[F, A],
      mkCtx: Metadata => F[A],
      serverOptions: ServerOptions
  ): ServerServiceDefinition

as all the other ones can be implemented in terms of:

  protected def serviceBinding[F[_]: Async, A](
    dispatcher: Dispatcher[F],
    serviceImpl: Service[F, A],
    mkCtx: Metadata => Resource[F, A],
    serverOptions: ServerOptions
): ServerServiceDefinition

Having the code gen generate only on method to implement that takes that resource thing. The adapter is:

val lifted: Metadata => Resource[F, A] = m => Resource.eval(mkCtx(m))

Copy link
Collaborator

@ahjohannessen ahjohannessen Sep 29, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Furthermore, mkCtx: Metadata => Resource[F, A] is more general and not that much of a breaking change for users if we change all the signature to express the acquisition of A in terms of a Resource[F, A] rather than F.


final def service[F[_]: Async, A](
dispatcher: Dispatcher[F],
serviceImpl: Service[F, A],
Expand Down