Tapir version: 1.10.10
Scala version: 2.13.14
Zio version: 2.1.5
Http4s version: 0.23.27
Circe version: 0.14.9
Doobie version: 1.0.0-RC5
Describe the bug
I cannot get the unit test to compile, and cannot reduce it to simpler code, since what seems to be an exactly equivalent unit test compiles correctly.
The compile error I'm getting (which contains all sorts of dependencies defined in my code) is:
type mismatch;
found : sttp.tapir.ztapir.ZServerEndpoint[com.joliciel.jochre.search.api.Types.Requirements,Any]
(which expands to) sttp.tapir.server.ServerEndpoint[Any,[β$0$]zio.ZIO[com.joliciel.jochre.search.core.config.AppConfig with doobie.util.transactor.Transactor[zio.Task] with com.joliciel.jochre.search.core.service.SearchService with com.joliciel.jochre.search.core.service.PreferenceService,Throwable,β$0$]]
required: sttp.tapir.server.ServerEndpoint[Any,[β$0$]zio.ZIO[com.joliciel.jochre.search.core.config.AppConfig with doobie.util.transactor.Transactor[[+A]zio.ZIO[Any,Throwable,A]] with com.joliciel.jochre.search.core.service.SearchService with com.joliciel.jochre.search.core.service.PreferenceService,Throwable,β$0$]]{type SECURITY_INPUT = this.SECURITY_INPUT; type PRINCIPAL = this.PRINCIPAL; type INPUT = this.INPUT; type ERROR_OUTPUT = this.ERROR_OUTPUT; type OUTPUT = this.OUTPUT}
.whenServerEndpoint(myEndpoint)
The only visible difference is that the compiler finds doobie.util.transactor.Transactor[zio.Task] but expects doobie.util.transactor.Transactor[[+A]zio.ZIO[Any,Throwable,A]], which is however exactly equivalent to the zio.Task type.
Also, there's the list of "types" at the end which I've never seen before, so I'm not sure what it represents: {type SECURITY_INPUT = this.SECURITY_INPUT; type PRINCIPAL = this.PRINCIPAL; type INPUT = this.INPUT; type ERROR_OUTPUT = this.ERROR_OUTPUT; type OUTPUT = this.OUTPUT}.
How to reproduce?
The full source code is here (branch add-http4s-tests), with the actual tests here.
I constructed several tests of increasing complexity. All of them pass except for the test of the real endpoint in my API.
These are all in modules/api/src/test/scala, in package com.joliciel.jochre.search.api.index.
First test, ZioTapirCirceTest, testing an endpoint with no ZIO dependencies. This works:
import io.circe.generic.auto._
import io.circe.syntax.EncoderOps
import sttp.client3._
import sttp.client3.impl.zio.RIOMonadAsyncError
import sttp.client3.testing.SttpBackendStub
import sttp.tapir.generic.auto._
import sttp.tapir.json.circe._
import sttp.tapir.server.stub.TapirStubInterpreter
import sttp.tapir.ztapir._
import zio.test.junit.JUnitRunnableSpec
import zio.test.{Spec, TestEnvironment, assertTrue}
import zio.{Scope, ZIO}
object ZioTapirCirceTest extends JUnitRunnableSpec {
case class Payload(one: String, two: String)
val myEndpoint =
endpoint.post.in(jsonBody[Payload]).out(stringBody).zServerLogic(r => ZIO.succeed(s"One ${r.one}. Two ${r.two}."))
val stub = TapirStubInterpreter(SttpBackendStub(new RIOMonadAsyncError[Any]))
.whenServerEndpoint(myEndpoint)
.thenRunLogic()
.backend()
val body = Payload("1", "2").asJson.noSpaces
override def spec: Spec[TestEnvironment with Scope, Any] = suite("ZioTapirCirceTest")(
test("work") {
for {
response <- basicRequest
.contentType("application/json")
.body(body)
.post(uri"http://test.com/test-request")
.send(stub)
} yield {
assertTrue(response.body.getOrElse("") == "One 1. Two 2.")
}
}
)
}
Second test ZioTapirCirceLayerTest, add a single dependency (as a ZIO layer). This works as well:
...
object ZioTapirCirceLayerTest extends JUnitRunnableSpec {
case class Payload(one: String, two: String)
trait Printer {
def print(s1: String, s2: String): String
}
object PrinterImpl extends Printer {
override def print(s1: String, s2: String): String = f"One $s1. Two $s2."
}
val printerLayer = ZLayer.succeed[Printer](PrinterImpl)
val myEndpoint: _root_.sttp.tapir.ztapir.ZServerEndpoint[Printer, Any] =
endpoint.post.in(jsonBody[Payload]).out(stringBody).zServerLogic[Printer] { payload =>
for {
printer <- ZIO.service[Printer]
} yield {
printer.print(payload.one, payload.two)
}
}
val stub = TapirStubInterpreter(SttpBackendStub(new RIOMonadAsyncError[Printer]))
.whenServerEndpoint(myEndpoint)
.thenRunLogic()
.backend()
val body = Payload("1", "2").asJson.noSpaces
override def spec: Spec[TestEnvironment with Scope, Any] = suite("ZioTapirCirceLayerTest")(
test("work") {
for {
response <- basicRequest
.contentType("application/json")
.body(body)
.post(uri"http://test.com/test-request")
.send(stub)
} yield {
assertTrue(response.body.getOrElse("") == "One 1. Two 2.")
}
}
).provideLayer(printerLayer)
}
Next test ZioTapirCirceTransactorLayerTest: add the Doobie Transactor as a dependency (since this is the only difference between the actual and expected type in the compilation failure). This works as well.
Next test ZioTapirCirceAllRequirementsTest: add all actual dependencies. This works as well.
...
object ZioTapirCirceAllRequirementsTest extends JUnitRunnableSpec with DatabaseTestBase with WithTestIndex {
case class Payload(one: String, two: String)
val myEndpoint: _root_.sttp.tapir.ztapir.ZServerEndpoint[Requirements, Any] =
endpoint.post.in(jsonBody[Payload]).out(stringBody).zServerLogic[Requirements] { payload =>
ZIO.succeed(f"One ${payload.one}. Two ${payload.two}.")
}
val stub = TapirStubInterpreter(SttpBackendStub(new RIOMonadAsyncError[Requirements]))
.whenServerEndpoint(myEndpoint)
.thenRunLogic()
.backend()
val body = Payload("1", "2").asJson.noSpaces
override def spec: Spec[TestEnvironment with Scope, Any] = suite("ZioTapirCirceAllRequirementsTest")(
test("work") {
for {
response <- basicRequest
.contentType("application/json")
.body(body)
.post(uri"http://test.com/test-request")
.send(stub)
} yield {
assertTrue(response.body.getOrElse("") == "One 1. Two 2.")
}
}
).provideLayer(
(transactorLayer ++ searchRepoLayer ++ suggestionRepoLayer ++ preferenceRepoLayer ++ indexLayer ++ YiddishFilters.live) >>> AppConfig.live ++ SearchService.live ++ PreferenceService.live ++ ZLayer
.service[Transactor[Task]]
) @@ TestAspect.sequential
}
Next test ZioTapirCirceSecureLayerTest: add securityIn, in case it makes a difference. Test passes.
...
object ZioTapirCirceSecureLayerTest extends JUnitRunnableSpec {
case class Payload(one: String, two: String)
trait Printer {
def print(user: String, s1: String, s2: String): String
}
object PrinterImpl extends Printer {
override def print(user: String, s1: String, s2: String): String = f"User $user. One $s1. Two $s2."
}
val printerLayer = ZLayer.succeed[Printer](PrinterImpl)
sealed trait HttpError
object HttpError {
case class Unauthorized(message: String) extends HttpError
}
val myEndpoint: _root_.sttp.tapir.ztapir.ZServerEndpoint[Printer, Any] =
endpoint
.securityIn(auth.bearer[String]())
.errorOut(
oneOf[HttpError](
oneOfVariant[HttpError.Unauthorized](
StatusCode.Unauthorized,
jsonBody[HttpError.Unauthorized].description("Invalid token or missing permissions")
)
)
)
.zServerSecurityLogic[Printer, String](bearer => ZIO.succeed(bearer))
.post
.in(jsonBody[Payload])
.out(stringBody)
.serverLogic[Printer] { user => payload =>
for {
printer <- ZIO.service[Printer]
} yield {
printer.print(user, payload.one, payload.two)
}
}
val stub = TapirStubInterpreter(SttpBackendStub(new RIOMonadAsyncError[Printer]))
.whenServerEndpoint(myEndpoint)
.thenRunLogic()
.backend()
val body = Payload("1", "2").asJson.noSpaces
override def spec: Spec[TestEnvironment with Scope, Any] = suite("ZioTapirCirceSecureLayerTest")(
test("work") {
for {
response <- basicRequest
.contentType("application/json")
.body(body)
.header(HeaderNames.Authorization, "Bearer Joe")
.post(uri"http://test.com/test-request")
.send(stub)
} yield {
assertTrue(response.body.getOrElse("") == "User Joe. One 1. Two 2.")
}
}
).provideLayer(printerLayer)
}
Next test ZioTapirCirceSecureAllRequirementsTest: security and all requirements. Test passes.
However, the "real" test, IndexAppTest, gives a compile error on line .whenServerEndpoint(myEndpoint) (the test isn't guaranteed to pass yet, I just want it to compile for now):
package com.joliciel.jochre.search.api.index
import com.joliciel.jochre.search.api.Types.Requirements
import com.joliciel.jochre.search.api.authentication.RawAuthenticationProviderForTestsOnly
import com.joliciel.jochre.search.core.config.AppConfig
import com.joliciel.jochre.search.core.service.{DatabaseTestBase, PreferenceService, SearchService, WithTestIndex}
import com.joliciel.jochre.search.yiddish.YiddishFilters
import doobie.Transactor
import sttp.client3._
import sttp.client3.impl.zio.RIOMonadAsyncError
import sttp.client3.testing.SttpBackendStub
import sttp.tapir.server.stub.TapirStubInterpreter
import zio.test.junit.JUnitRunnableSpec
import zio.test.{Spec, TestAspect, TestEnvironment, assertTrue}
import zio.{Scope, Task, ZLayer}
import scala.concurrent.ExecutionContext
object IndexAppTest extends JUnitRunnableSpec with DatabaseTestBase with WithTestIndex {
override def spec: Spec[TestEnvironment with Scope, Any] = suite("IndexAppTest")(
test("call putPdfHttp endpoint") {
val authenticationProvider = RawAuthenticationProviderForTestsOnly
val executionContext = ExecutionContext.global
val indexApp = IndexApp(authenticationProvider, executionContext)
val myEndpoint: _root_.sttp.tapir.ztapir.ZServerEndpoint[Requirements, Any] = indexApp.putPdfHttp
val stub = TapirStubInterpreter(SttpBackendStub(new RIOMonadAsyncError[Requirements]))
.whenServerEndpoint(myEndpoint)
.thenRunLogic()
.backend()
val pdfStream = getClass.getResourceAsStream("/nybc200089-11-12.pdf")
val altoStream = getClass.getResourceAsStream("/nybc200089-11-12_alto4.zip")
val metadataStream = getClass.getResourceAsStream("/nybc200089_meta.xml")
for {
response <- basicRequest
.contentType("application/json")
.multipartBody(
multipart("docReference", "nybc200089"),
multipart("pdfFile", pdfStream),
multipart("altoFile", altoStream),
multipart("metadataFile", metadataStream)
)
.post(uri"http://test.com/test-request")
.send(stub)
} yield {
assertTrue(response.body.getOrElse("") == """{"pages": 2}""")
}
}
).provideLayer(
(transactorLayer ++ searchRepoLayer ++ suggestionRepoLayer ++ preferenceRepoLayer ++ indexLayer ++ YiddishFilters.live) >>> AppConfig.live ++ SearchService.live ++ PreferenceService.live ++ ZLayer
.service[Transactor[Task]]
) @@ TestAspect.sequential
}
Any ideas how to manage to get this compiling?
I don't see any difference between the type for myEndpoint in the final test and in the previous tests.
Tapir version: 1.10.10
Scala version: 2.13.14
Zio version: 2.1.5
Http4s version: 0.23.27
Circe version: 0.14.9
Doobie version: 1.0.0-RC5
Describe the bug
I cannot get the unit test to compile, and cannot reduce it to simpler code, since what seems to be an exactly equivalent unit test compiles correctly.
The compile error I'm getting (which contains all sorts of dependencies defined in my code) is:
The only visible difference is that the compiler finds
doobie.util.transactor.Transactor[zio.Task]but expectsdoobie.util.transactor.Transactor[[+A]zio.ZIO[Any,Throwable,A]], which is however exactly equivalent to thezio.Tasktype.Also, there's the list of "types" at the end which I've never seen before, so I'm not sure what it represents:
{type SECURITY_INPUT = this.SECURITY_INPUT; type PRINCIPAL = this.PRINCIPAL; type INPUT = this.INPUT; type ERROR_OUTPUT = this.ERROR_OUTPUT; type OUTPUT = this.OUTPUT}.How to reproduce?
The full source code is here (branch
add-http4s-tests), with the actual tests here.I constructed several tests of increasing complexity. All of them pass except for the test of the real endpoint in my API.
These are all in
modules/api/src/test/scala, in packagecom.joliciel.jochre.search.api.index.First test,
ZioTapirCirceTest, testing an endpoint with no ZIO dependencies. This works:Second test
ZioTapirCirceLayerTest, add a single dependency (as a ZIO layer). This works as well:Next test
ZioTapirCirceTransactorLayerTest: add the Doobie Transactor as a dependency (since this is the only difference between the actual and expected type in the compilation failure). This works as well.Next test
ZioTapirCirceAllRequirementsTest: add all actual dependencies. This works as well.Next test
ZioTapirCirceSecureLayerTest: add securityIn, in case it makes a difference. Test passes.Next test
ZioTapirCirceSecureAllRequirementsTest: security and all requirements. Test passes.However, the "real" test,
IndexAppTest, gives a compile error on line.whenServerEndpoint(myEndpoint)(the test isn't guaranteed to pass yet, I just want it to compile for now):Any ideas how to manage to get this compiling?
I don't see any difference between the type for
myEndpointin the final test and in the previous tests.