diff --git a/openapi-codegen/core/src/main/scala/sttp/tapir/codegen/EndpointGenerator.scala b/openapi-codegen/core/src/main/scala/sttp/tapir/codegen/EndpointGenerator.scala index 5f88c23bc0..7b388ffc93 100644 --- a/openapi-codegen/core/src/main/scala/sttp/tapir/codegen/EndpointGenerator.scala +++ b/openapi-codegen/core/src/main/scala/sttp/tapir/codegen/EndpointGenerator.scala @@ -309,35 +309,55 @@ class EndpointGenerator { // .errorOut(stringBody) // .out(jsonBody[List[Book]]) - responses - .map { resp => - val d = s""".description("${JavaEscape.escapeString(resp.description)}")""" + val (outs, errorOuts) = responses.partition { resp => + resp.content match { + case Nil | _ +: Nil => + resp.code match { + case okStatus(_) => true + case "default" | errorStatus(_) => false + case x => bail(s"Statuscode mapping is incomplete! Cannot handle $x") + } + case _ => bail("We can handle only one return content!") + } + } + def bodyFmt(resp: OpenapiResponse): String = { + val d = s""".description("${JavaEscape.escapeString(resp.description)}")""" + resp.content match { + case Nil => "" + case content +: Nil => + s"${contentTypeMapper(content.contentType, content.schema, streamingImplementation)}$d" + } + } + def mappedGroup(group: Seq[OpenapiResponse]) = group match { + case Nil => None + case resp +: Nil => resp.content match { case Nil => + val d = s""".description("${JavaEscape.escapeString(resp.description)}")""" resp.code match { - case "200" | "default" => "" - case okStatus(s) => s".out(statusCode(sttp.model.StatusCode($s))$d)" - case errorStatus(s) => s".errorOut(statusCode(sttp.model.StatusCode($s))$d)" - } - case content +: Nil => - resp.code match { - case "200" => - s".out(${contentTypeMapper(content.contentType, content.schema, streamingImplementation)}$d)" - case okStatus(s) => - s".out(${contentTypeMapper(content.contentType, content.schema, streamingImplementation)}$d.and(statusCode(sttp.model.StatusCode($s))))" - case "default" => - s".errorOut(${contentTypeMapper(content.contentType, content.schema, streamingImplementation)}$d)" - case errorStatus(s) => - s".errorOut(${contentTypeMapper(content.contentType, content.schema, streamingImplementation)}$d.and(statusCode(sttp.model.StatusCode($s))))" - case x => - bail(s"Statuscode mapping is incomplete! Cannot handle $x") + case "200" | "default" => None + case okStatus(s) => Some(s"statusCode(sttp.model.StatusCode($s))$d") + case errorStatus(s) => Some(s"statusCode(sttp.model.StatusCode($s))$d") } - case _ => bail("We can handle only one return content!") + case _ => + Some(resp.code match { + case "200" | "default" => s"${bodyFmt(resp)}" + case okStatus(s) => s"${bodyFmt(resp)}.and(statusCode(sttp.model.StatusCode($s)))" + case errorStatus(s) => s"${bodyFmt(resp)}.and(statusCode(sttp.model.StatusCode($s)))" + }) } - } - .sorted - .filter(_.nonEmpty) - .mkString("\n") + case many => + if (many.map(_.code).distinct.size != many.size) bail("Cannot construct schema for multiple responses with same status code") + val oneOfs = many.map { m => + val code = if (m.code == "default") "400" else m.code + s"oneOfVariant(sttp.model.StatusCode(${code}), ${bodyFmt(m)})" + } + Some(s"oneOf(${oneOfs.mkString(", ")})") + } + val mappedOuts = mappedGroup(outs).map(s => s".out($s)") + val mappedErrorOuts = mappedGroup(errorOuts).map(s => s".errorOut($s)") + + Seq(mappedErrorOuts, mappedOuts).flatten.mkString("\n") } private def contentTypeMapper( diff --git a/openapi-codegen/core/src/main/scala/sttp/tapir/codegen/SchemaGenerator.scala b/openapi-codegen/core/src/main/scala/sttp/tapir/codegen/SchemaGenerator.scala index cf9b2582ac..7d6bc95434 100644 --- a/openapi-codegen/core/src/main/scala/sttp/tapir/codegen/SchemaGenerator.scala +++ b/openapi-codegen/core/src/main/scala/sttp/tapir/codegen/SchemaGenerator.scala @@ -54,7 +54,10 @@ object SchemaGenerator { false ) -> "implicit lazy val anyTapirSchema: sttp.tapir.Schema[io.circe.Json] = sttp.tapir.Schema.any[io.circe.Json]" ) - else throw new NotImplementedError("any not implemented for json libs other than circe") + else + throw new NotImplementedError( + s"any not implemented for json libs other than circe (problematic models: ${schemasWithAny.map(_._1)})" + ) val openApiSchemasWithTapirSchemas = doc.components .map(_.schemas.map { case (name, _: OpenapiSchemaEnum) => diff --git a/openapi-codegen/core/src/test/scala/sttp/tapir/codegen/TestHelpers.scala b/openapi-codegen/core/src/test/scala/sttp/tapir/codegen/TestHelpers.scala index 34094f11dd..b07beca85c 100644 --- a/openapi-codegen/core/src/test/scala/sttp/tapir/codegen/TestHelpers.scala +++ b/openapi-codegen/core/src/test/scala/sttp/tapir/codegen/TestHelpers.scala @@ -100,6 +100,12 @@ object TestHelpers { | type: array | items: | $ref: '#/components/schemas/Book' + | '401': + | description: 'unauthorized' + | content: + | text/plain: + | schema: + | type: string | default: | description: '' | content: @@ -165,6 +171,7 @@ object TestHelpers { "", Seq(OpenapiResponseContent("application/json", OpenapiSchemaArray(OpenapiSchemaRef("#/components/schemas/Book"), false))) ), + OpenapiResponse("401", "unauthorized", Seq(OpenapiResponseContent("text/plain", OpenapiSchemaString(false)))), OpenapiResponse("default", "", Seq(OpenapiResponseContent("text/plain", OpenapiSchemaString(false)))) ), requestBody = None,