You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I'm not sure, if it is an wrong usage of tapir by us or a real issue in the library.
When using oneOfDefaultVariant as the last variant in a OneOfVariant and having a sealed trait error type as output error, a ClassCastException gets thrown, because the returned error is tried to be cast to the default one. This doesn't happen, if oneOfDefaultVariant isn't used. If oneOfVariant is used instead, the interpreter behaves correctly. This issue was observed in our production system and can be reproduced with the provided test code.
How to reproduce?
package api.http
import api.http.ServiceEndpoint.servEndpoint
import io.circe.generic.auto._
import org.scalatest.flatspec.AsyncFlatSpec
import org.scalatest.matchers.must.Matchers
import sttp.capabilities.WebSockets
import sttp.client3.testing.SttpBackendStub
import sttp.client3.{ Response, UriContext }
import sttp.model.StatusCode
import sttp.model.StatusCode.InternalServerError
import sttp.tapir.client.sttp.SttpClientInterpreter
import sttp.tapir.client.sttp.WebSocketToPipe.webSocketsNotSupported
import sttp.tapir.generic.auto._
import sttp.tapir.json.circe._
import sttp.tapir.server.stub.TapirStubInterpreter
import sttp.tapir.ztapir._
import sttp.tapir.{ DecodeResult, EndpointOutput, PublicEndpoint }
import scala.concurrent.Future
class ClassCastApiDefinitionSpec extends AsyncFlatSpec with Matchers {
it must "response with error code 404" in {
responseWith(serverBehavior = _.thenRespondError(NotFound))
.map(_.code mustBe StatusCode.NotFound)
}
it must "response with error code 500 on internal error" in {
responseWith(serverBehavior = _.thenRespondError(ServiceInternalError()))
.map(_.code mustBe InternalServerError)
}
type EndpointStub = TapirStubInterpreter[Future, WebSockets, Unit]#TapirEndpointStub[String, ServiceFailure, User]
type StubInterpreter = TapirStubInterpreter[Future, WebSockets, Unit]
private def responseWith(
serverBehavior: EndpointStub => StubInterpreter
): Future[Response[DecodeResult[Either[ServiceFailure, User]]]] = {
val endpointStub = TapirStubInterpreter(SttpBackendStub.asynchronousFuture).whenEndpoint(servEndpoint)
val stub = serverBehavior(endpointStub).backend()
SttpClientInterpreter()
.toRequest(servEndpoint, Some(uri"http://test.com"))(webSocketsNotSupported[Any])("1")
.send(stub)
}
}
case class User(id: String, name: String)
sealed trait ServiceFailure
final case class ServiceInternalError(msg: String = "internal error") extends ServiceFailure
object NotFound extends ServiceFailure
object ServiceEndpoint {
private val oneOfErrorVariants =
oneOf(
oneOfVariant(
statusCode(StatusCode.NotFound).and(emptyOutputAs(NotFound).description("User not found"))
),
oneOfDefaultVariant(
statusCode(StatusCode.InternalServerError).and(
jsonBody[ServiceInternalError].description("Internal server error")
)
)
)
val servEndpoint: PublicEndpoint[String, ServiceFailure, User, Any] =
endpoint.get
.in("users" / path[String].name("id"))
.out(statusCode(StatusCode.Ok).and(jsonBody[User]))
.errorOut(oneOfErrorVariants)
}
Additional information
The text was updated successfully, but these errors were encountered:
Tapir version: 0.20.2
Scala version: 2.13.8
Describe the bug
I'm not sure, if it is an wrong usage of tapir by us or a real issue in the library.
When using oneOfDefaultVariant as the last variant in a OneOfVariant and having a sealed trait error type as output error, a ClassCastException gets thrown, because the returned error is tried to be cast to the default one. This doesn't happen, if oneOfDefaultVariant isn't used. If oneOfVariant is used instead, the interpreter behaves correctly. This issue was observed in our production system and can be reproduced with the provided test code.
How to reproduce?
Additional information
The text was updated successfully, but these errors were encountered: