TapirSchemaToJsonSchema does not resolve the root reference

We have a coproduct where the root trait is referenced from a child. When we try to convert auto-generated tapir schema to apispec json schema the child schemas are referenced from $defs object but not the root schema.

In tapir/docs/apispec-docs/src/main/scala/sttp/tapir/docs/apispec/schema/TapirSchemaToJsonSchema.scala at 303bb741d762deb1c92639667e0dc9832304bbab · softwaremill/tapir · GitHub
the root schema reference is actually dropped.

The reference remains as a class full name and that does not work in validation where a correct URI is expected.

Is there some simple solution or workaround? It works for other recursive hierarchies where the root is not referenced. So one solution would be to introduce a root wrapper and then to remove the wrapper from the resulting Json schema (not that simple).

Example:

import com.networknt.schema.*
import com.networknt.schema.SpecVersion.VersionFlag
import com.networknt.schema.{ InputFormat, JsonSchema }
import io.circe.generic.extras.Configuration
import io.circe.{ Printer, Encoder }
import io.circe.syntax.*
import io.circe.generic.extras.semiauto.*
import sttp.apispec.circe.*
import sttp.tapir.Schema
import sttp.tapir.docs.apispec.schema.TapirSchemaToJsonSchema
import sttp.tapir.generic.auto.SchemaDerivation

object unresolved extends SchemaDerivation {
  sealed trait Model
  final case class SetModel(set: Set[Model]) extends Model
  final case class LeafModel(num: Int)       extends Model

  implicit val configuration: Configuration         = Configuration.default
  implicit val setModelEncoder: Encoder[SetModel]   = deriveConfiguredEncoder
  implicit val leafModelEncoder: Encoder[LeafModel] = deriveConfiguredEncoder
  implicit val modelEncoder: Encoder[Model] = Encoder.instance {
    case x: SetModel  => x.asJson
    case x: LeafModel => x.asJson
  }

  def main(args: Array[String]): Unit = {
    val printer: Printer            = Printer.spaces2
    val jsf: JsonSchemaFactory      = JsonSchemaFactory.getInstance(VersionFlag.V202012)
    val svc: SchemaValidatorsConfig = SchemaValidatorsConfig.builder().build()

    val schema           = implicitly[Schema[Model]]
    val apispecSchema    = TapirSchemaToJsonSchema(schema, markOptionsAsNullable = true)
    val jsonSchema       = apispecSchema.asJson
    val jsonSchemaString = jsonSchema.printWith(printer)
    println(s"schema: $jsonSchemaString")

    val sample       = SetModel(Set(LeafModel(1), LeafModel(2)))
    val sampleString = sample.asJson.printWith(printer)
    println(s"sample = $sampleString")

    val schemaValidator: JsonSchema = jsf.getSchema(jsonSchemaString, svc)
    val result                      = schemaValidator.validate(sampleString, InputFormat.JSON, OutputFormat.DEFAULT)
    println(s"result = ${result.toArray.mkString("\n")}")
  }
}

Can you create an issue with the actual, and expected schemas that should be created in this case? I suppose there is a way in json schema to reference the root?

1 Like

Thank you Adam, I created the issue.