multipartBody inferring Product as type in wart remover

While working on upgrading our Tapir version I have run into an issue where Wartremover infers a Product type on our multipartbody. I have some example code with the issue:

//> using scala 2.13
//> using lib com.softwaremill.sttp.tapir::tapir-core:1.8.4
//> using lib org.scala-lang.modules::scala-xml:1.3.0
//> using lib io.circe::circe-generic:0.14.6
//> using plugin org.wartremover:::wartremover:3.1.5
//> using option "-P:wartremover:traverser:org.wartremover.warts.Product"

import io.circe.Codec
import io.circe.generic.semiauto.deriveCodec
import sttp.tapir._
import sttp.model.Part
import sttp.tapir.Schema


object Example {

  final case class Model(int: Int)
  final case class multiExample(model: Model, part: Part[File])

  object Model {
    implicit val modelCodec: Codec[Model] = deriveCodec
    implicit lazy val modelSchema: Schema[Model] = Schema.derived[Model]
  object multiExample {
    implicit lazy val multiExampleSchema: Schema[multiExample] = Schema.derived[multiExample]

  val example =

The above works fine until for any version lower then 0.20.0, but breaks afterwards. Thinking it might just be wartremover, I have tried suppressing the warning, however that resulted in the endpoint not being accessible at all.
In my testing I have also noticed that this only happens if you have a custom class within the class in the multipartBody, e.g. if in the above you change the model: Model to model: String, this will compile without any errors.

Does anyone have any clue as to why this might be, and possibly some kind of answer on how to solve the problem?

Sorry for the (very late) reply :slight_smile:

If you still have problems with this … am I guessing right that the model field should be deserialized from JSON?

If so, you’re missing importing the tapir ↔ circe integration, which brings into scope tapir’s codecs (as in sttp.tapir.Codec). Right now, the invocation multipartBody[multiExample] looks for any codec for each field. In case of the model field, no codecs are explicitly imported, so it looks in the defaults - and finds a form-field codec. So this code kind of expects nested forms. By adding import sttp.tapir.json.circe._ I get the correct behavior.

(btw. - everything compiles fine)