Differentiating between undefined and null

Indeed, you are right. The nullable property is only available on sttp.apispec.Schema type - which represents the JSON Schema data structure. This is generated from sttp.tapir.Schema[T], which describes a type. The tapir schema doesn’t have the nullable property, and it can currently only be set globally.

As a work-around, you can modify the OpenAPI data structure that the interpreter returns. It’s not the prettiest, but does the job. For example:

  case class Test(s: Option[String])

  test("x") {
    import com.softwaremill.quicklens._
    val e = endpoint.in(jsonBody[Test])
    val openapi = OpenAPIDocsInterpreter().toOpenAPI(List(e), Info("Fruits", "1.0"))
    val openapi2 = openapi.modify(
      _.components.each.schemas.at("Test").eachRight.when[sttp.apispec.Schema
       .properties.at("s").eachRight.when[sttp.apispec.Schema]
       .nullable
    ).setTo(Some(true))
    val actualYaml = openapi2.toYaml
    println(actualYaml)
  }

As for a more user-friendly fix, I think we should have an option to customise the final, generated JSON Schema (which is part of the OpenAPI object). We could set an attribute on the tapir-schema, with a function which would be called by the OpenAPI interpreter.

Can you create an issue to implement this?