diff --git a/openapi-codegen/core/src/main/scala/sttp/tapir/codegen/endpoints/InAndOutComponents.scala b/openapi-codegen/core/src/main/scala/sttp/tapir/codegen/endpoints/InAndOutComponents.scala index a09f61a36d..dc7d6e616a 100644 --- a/openapi-codegen/core/src/main/scala/sttp/tapir/codegen/endpoints/InAndOutComponents.scala +++ b/openapi-codegen/core/src/main/scala/sttp/tapir/codegen/endpoints/InAndOutComponents.scala @@ -52,6 +52,11 @@ object InAndOutComponents { private[endpoints] def aliases(packageReuse: PackageReuseContext, types: Seq[String], seperateFilesForModels: Boolean): String = types.map(PackageReuseContext.enumAliasType(_, packageReuse, seperateFilesForModels)).mkString("\n") + private def mkWrapHelper(required: Boolean)(s: String): String = + if (required) s else s"$s.map(Option(_))(_.orNull)" + private def mkWrapTypeHelper(required: Boolean)(s: String): String = + if (required) s else s"Option[$s]" + private[endpoints] def contentTypeMapper( contentType: String, schema: OpenapiSchemaType, @@ -64,6 +69,8 @@ object InAndOutComponents { tapirCodegenDirectives: Set[String], validators: ValidationDefns )(implicit location: Location): MappedContentType = { + def wrapHelper: String => String = mkWrapHelper(required) + def wrapTypeHelper: String => String = mkWrapTypeHelper(required) def vRef(t: OpenapiSchemaType, r: Boolean) = { val maybeValidatorRef = t match { case r: OpenapiSchemaRef => Some(r.stripped) @@ -88,13 +95,14 @@ object InAndOutComponents { (tapirCodegenDirectives.contains(forceEager) && position != Err) || (tapirCodegenDirectives.contains(forceReqEager) && position == Request) || (tapirCodegenDirectives.contains(forceRespEager) && position == Response)) + def mkMapContentType(i: String, t: String) = MappedContentType(wrapHelper(i), wrapTypeHelper(t)) contentType match { case any if streaming => - failoverBinaryCase(endpointName, position, any, schema, false, streamingImplementation) + failoverBinaryCase(endpointName, position, any, schema, false, streamingImplementation, required) case "text/plain" => - MappedContentType("stringBody", "String") + mkMapContentType("stringBody", "String") case "text/html" => - MappedContentType("htmlBodyUtf8", "String") + mkMapContentType("htmlBodyUtf8", "String") case ct if ContentTypes.isXml(ct) && xmlSerdeLib != XmlSerdeLib.NoSupport => val (outT: String, maybeInline: Option[String], maybeAlias: Option[String], maybeTpe: Seq[String]) = schema match { case st: OpenapiSchemaSimpleType => @@ -115,8 +123,7 @@ object InAndOutComponents { MappedContentType(bodyType + v(required), req, maybeInline, maybeTpe) case ct if ContentTypes.isJson(ct) && tapirCodegenDirectives.contains(jsonBodyAsString) => val body = if (ContentTypes.isSuffixed(ct)) suffixedBody("sttp.tapir.Codec.string", ct) else "stringJsonBody" - if (required) MappedContentType(body, "String", None) - else MappedContentType(s"$body.map(Option(_))(_.orNull)", "Option[String]", None) + mkMapContentType(body, "String") case ct if ContentTypes.isJson(ct) => val (outT, maybeInline) = schema match { case st: OpenapiSchemaSimpleType => @@ -140,15 +147,15 @@ object InAndOutComponents { case "multipart/form-data" => schema match { case _: OpenapiSchemaBinary => - MappedContentType("multipartBody", "Seq[Part[Array[Byte]]]") + mkMapContentType("multipartBody", "Seq[Part[Array[Byte]]]") case schemaRef: OpenapiSchemaRef => val (t, _) = mapSchemaSimpleTypeToType(schemaRef, multipartForm = true) - MappedContentType(s"multipartBody[$t]" + v(required), t) + MappedContentType(wrapHelper(s"multipartBody[$t]") + v(required), wrapTypeHelper(t)) case schemaRef: OpenapiSchemaObject if schemaRef.properties.forall(_._2.`type`.isInstanceOf[OpenapiSchemaSimpleType]) => val (inlineClassName, inlineClassDefn) = inlineDefn(endpointName, position, schemaRef) MappedContentType( - s"multipartBody[$inlineClassName]" + v(required), - inlineClassName, + wrapHelper(s"multipartBody[$inlineClassName]") + v(required), + wrapTypeHelper(inlineClassName), inlineClassDefn, inlineClassDefn.map(_ => inlineClassName).toSeq ) @@ -161,7 +168,8 @@ object InAndOutComponents { other, schema, eager, - streamingImplementation + streamingImplementation, + required ) } } @@ -172,8 +180,11 @@ object InAndOutComponents { contentType: String, schema: OpenapiSchemaType, isEager: Boolean, - streamingImplementation: StreamingImplementation + streamingImplementation: StreamingImplementation, + required: Boolean )(implicit location: Location): MappedContentType = { + def wrapHelper: String => String = mkWrapHelper(required) + def wrapTypeHelper: String => String = mkWrapTypeHelper(required) def codec(baseType: String, contentType: String) = { val cf = codecFormat(contentType) val schema = if (baseType == "Array[Byte]") "Schema.schemaForByteArray" else "Schema.schemaForString" @@ -195,7 +206,7 @@ object InAndOutComponents { case "application/zip" => "CodecFormat.Zip()" case o => codecFormat(o).instance } - if (isEager) MappedContentType(eagerBody, if (contentType.startsWith("text/")) "String" else "Array[Byte]") + if (isEager) MappedContentType(wrapHelper(eagerBody), wrapTypeHelper(if (contentType.startsWith("text/")) "String" else "Array[Byte]")) else { val capability = capabilityImpl(streamingImplementation) val tpe = capabilityType(streamingImplementation) diff --git a/openapi-codegen/core/src/main/scala/sttp/tapir/codegen/endpoints/OutComponent.scala b/openapi-codegen/core/src/main/scala/sttp/tapir/codegen/endpoints/OutComponent.scala index 6905d61cc8..4291cf0569 100644 --- a/openapi-codegen/core/src/main/scala/sttp/tapir/codegen/endpoints/OutComponent.scala +++ b/openapi-codegen/core/src/main/scala/sttp/tapir/codegen/endpoints/OutComponent.scala @@ -304,6 +304,8 @@ object OutComponent { headerDefns(targetScala3, jsonSerdeLib, doc, generateValidators, ambiguous, isErrorPosition)(endpointName, many) val noAmbiguity = noHeaders && !ambiguous + val bodyIsStreaming = (!isErrorPosition && tapirCodegenDirectives.contains(forceRespStreaming)) || + (!isErrorPosition && tapirCodegenDirectives.contains(forceStreaming)) val (oneOfs, types, inlineDefns) = many.map { m => val (decl, maybeBodyType, inlineDefn1) = bodyFmt(m, isErrorPosition, optional = contentCanBeEmpty) @@ -358,8 +360,10 @@ object OutComponent { val (_, nonOptionalType, _) = bodyFmt(m, isErrorPosition) val maybeMap = if (m.content.size > 1 || tpeIsBin) ".map(Some(_))(_.orNull)" else "" val someType = nonOptionalType.map(": " + _.replaceAll("^Option\\[(.+)]$", "$1")).getOrElse("") + val tpesAreBin = someType.contains("BinaryStream") || someType.contains("fs2.Stream") + def maybeUnchecked = if (tpesAreBin) " @scala.unchecked" else "" ( - s"oneOfVariantValueMatcher($maybeCode$decl$maybeStrict$maybeMap$h){ case ${matchBodyAndHeaders(s"Some(_$someType)")} => true }", + s"oneOfVariantValueMatcher($maybeCode$decl$maybeStrict$maybeMap$h){ case ${matchBodyAndHeaders(s"Some(_$someType$maybeUnchecked)")} => true }", maybeBodyType, inlineDefn1 ) @@ -393,8 +397,6 @@ object OutComponent { .map { case (k, vs) => k -> vs.map(_._2) } .toMap val traitName = s"${endpointName.capitalize}Body${if (isErrorPosition) "Err" else "Out"}" - val bodyIsStreaming = (!isErrorPosition && tapirCodegenDirectives.contains(forceRespStreaming)) || - (!isErrorPosition && tapirCodegenDirectives.contains(forceStreaming)) val bodyIsEager = !bodyIsStreaming && (isErrorPosition || (!isErrorPosition && tapirCodegenDirectives.contains(forceRespEager)) || (!isErrorPosition && tapirCodegenDirectives.contains(forceEager))) diff --git a/openapi-codegen/sbt-plugin/src/sbt-test/sbt-openapi-codegen/nifi_test/Expected.scala.txt b/openapi-codegen/sbt-plugin/src/sbt-test/sbt-openapi-codegen/nifi_test/Expected.scala.txt index b8a14b1426..b0e269a1c2 100644 --- a/openapi-codegen/sbt-plugin/src/sbt-test/sbt-openapi-codegen/nifi_test/Expected.scala.txt +++ b/openapi-codegen/sbt-plugin/src/sbt-test/sbt-openapi-codegen/nifi_test/Expected.scala.txt @@ -359,7 +359,7 @@ object TapirGeneratedEndpoints { .post .in(("controller-services" / path[String]("id").description("The controller service id.") / "state" / "clear-requests")) .in(oneOfBody[ClearState_1BodyIn]( - EndpointIO.Body(RawBodyType.ByteArrayBody, Codec.id[Array[Byte], `*/*CodecFormat`](`*/*CodecFormat`(), Schema.schemaForByteArray), EndpointIO.Info.empty).map(ClearState_1Body0In(_))(_.value).widenBody[ClearState_1BodyIn].description("Optional component state to perform a selective key removal. If omitted, clears all state."), + EndpointIO.Body(RawBodyType.ByteArrayBody, Codec.id[Array[Byte], `*/*CodecFormat`](`*/*CodecFormat`(), Schema.schemaForByteArray), EndpointIO.Info.empty).map(Option(_))(_.orNull).map(ClearState_1BodyOption_Array_Byte__In(_))(_.value).widenBody[ClearState_1BodyIn].description("Optional component state to perform a selective key removal. If omitted, clears all state."), jsonBody[Option[ComponentStateEntity]].map(ClearState_1BodyOption_ComponentStateEntity_In(_))(_.value).widenBody[ClearState_1BodyIn].description("Optional component state to perform a selective key removal. If omitted, clears all state."))) .errorOut(oneOf[Unit]( oneOfVariantSingletonMatcher(sttp.model.StatusCode(400), emptyOutput.description("NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."))(()), @@ -684,7 +684,7 @@ object TapirGeneratedEndpoints { .post .in(("controller" / "flow-analysis-rules" / path[String]("id").description("The flow analysis rule id.") / "state" / "clear-requests")) .in(oneOfBody[ClearStateBodyIn]( - EndpointIO.Body(RawBodyType.ByteArrayBody, Codec.id[Array[Byte], `*/*CodecFormat`](`*/*CodecFormat`(), Schema.schemaForByteArray), EndpointIO.Info.empty).map(ClearStateBody0In(_))(_.value).widenBody[ClearStateBodyIn].description("Optional component state to perform a selective key removal. If omitted, clears all state."), + EndpointIO.Body(RawBodyType.ByteArrayBody, Codec.id[Array[Byte], `*/*CodecFormat`](`*/*CodecFormat`(), Schema.schemaForByteArray), EndpointIO.Info.empty).map(Option(_))(_.orNull).map(ClearStateBodyOption_Array_Byte__In(_))(_.value).widenBody[ClearStateBodyIn].description("Optional component state to perform a selective key removal. If omitted, clears all state."), jsonBody[Option[ComponentStateEntity]].map(ClearStateBodyOption_ComponentStateEntity_In(_))(_.value).widenBody[ClearStateBodyIn].description("Optional component state to perform a selective key removal. If omitted, clears all state."))) .errorOut(oneOf[Unit]( oneOfVariantSingletonMatcher(sttp.model.StatusCode(400), emptyOutput.description("NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."))(()), @@ -1116,7 +1116,7 @@ object TapirGeneratedEndpoints { oneOfVariantSingletonMatcher(sttp.model.StatusCode(503), emptyOutput.description("NiFi instance is not ready for serving request, or temporarily overloaded. Retrying the same request later may be successful"))(()), oneOfVariantSingletonMatcher(sttp.model.StatusCode(401), emptyOutput.description("Client could not be authenticated."))(()))) .out(oneOf[Option[sttp.capabilities.pekko.PekkoStreams.BinaryStream]]( - oneOfVariantValueMatcher(sttp.model.StatusCode(202), streamBody(sttp.capabilities.pekko.PekkoStreams)(Schema.binary[StreamingOutput], CodecFormat.OctetStream()).description("").toEndpointIO.map(Some(_))(_.orNull)){ case Some(_: sttp.capabilities.pekko.PekkoStreams.BinaryStream) => true }, + oneOfVariantValueMatcher(sttp.model.StatusCode(202), streamBody(sttp.capabilities.pekko.PekkoStreams)(Schema.binary[StreamingOutput], CodecFormat.OctetStream()).description("").toEndpointIO.map(Some(_))(_.orNull)){ case Some(_: sttp.capabilities.pekko.PekkoStreams.BinaryStream @scala.unchecked) => true }, oneOfVariantSingletonMatcher(sttp.model.StatusCode(200), emptyOutput.description("There is no flow file to return."))(None))) .tags(List("DataTransfer")) @@ -2162,7 +2162,7 @@ object TapirGeneratedEndpoints { oneOfVariantSingletonMatcher(sttp.model.StatusCode(401), emptyOutput.description("Client could not be authenticated."))(()))) .out(oneOf[(Option[sttp.capabilities.pekko.PekkoStreams.BinaryStream], DownloadFlowFileContentResponseHeader)]( oneOfVariantValueMatcher(sttp.model.StatusCode(206), emptyOutputAs(None).description("Partial Content with range of bytes requested").and(emptyOutputAs(DownloadFlowFileContentResponseHeader206))){ case (None, DownloadFlowFileContentResponseHeader206) => true}, - oneOfVariantValueMatcher(sttp.model.StatusCode(200), streamBody(sttp.capabilities.pekko.PekkoStreams)(Schema.binary[StreamingOutput], `*/*CodecFormat`()).description("").toEndpointIO.map(Some(_))(_.orNull).and(header[String]("Content-Type").validate(Validator.pattern("([^*]+|[*])/([^*]+|[*])")).map(DownloadFlowFileContentResponseHeader200(_))(_.`Content-Type`))){ case (Some(_: sttp.capabilities.pekko.PekkoStreams.BinaryStream), (_: DownloadFlowFileContentResponseHeader200)) => true })) + oneOfVariantValueMatcher(sttp.model.StatusCode(200), streamBody(sttp.capabilities.pekko.PekkoStreams)(Schema.binary[StreamingOutput], `*/*CodecFormat`()).description("").toEndpointIO.map(Some(_))(_.orNull).and(header[String]("Content-Type").validate(Validator.pattern("([^*]+|[*])/([^*]+|[*])")).map(DownloadFlowFileContentResponseHeader200(_))(_.`Content-Type`))){ case (Some(_: sttp.capabilities.pekko.PekkoStreams.BinaryStream @scala.unchecked), (_: DownloadFlowFileContentResponseHeader200)) => true })) .tags(List("FlowFileQueues")) type CreateFlowFileListingEndpoint = Endpoint[Unit, String, Unit, ListingRequestEntity, Any] @@ -2841,7 +2841,7 @@ object TapirGeneratedEndpoints { .post .in(("parameter-providers" / path[String]("id").description("The parameter provider id.") / "state" / "clear-requests")) .in(oneOfBody[ClearState_2BodyIn]( - EndpointIO.Body(RawBodyType.ByteArrayBody, Codec.id[Array[Byte], `*/*CodecFormat`](`*/*CodecFormat`(), Schema.schemaForByteArray), EndpointIO.Info.empty).map(ClearState_2Body0In(_))(_.value).widenBody[ClearState_2BodyIn].description("Optional component state to perform a selective key removal. If omitted, clears all state."), + EndpointIO.Body(RawBodyType.ByteArrayBody, Codec.id[Array[Byte], `*/*CodecFormat`](`*/*CodecFormat`(), Schema.schemaForByteArray), EndpointIO.Info.empty).map(Option(_))(_.orNull).map(ClearState_2BodyOption_Array_Byte__In(_))(_.value).widenBody[ClearState_2BodyIn].description("Optional component state to perform a selective key removal. If omitted, clears all state."), jsonBody[Option[ComponentStateEntity]].map(ClearState_2BodyOption_ComponentStateEntity_In(_))(_.value).widenBody[ClearState_2BodyIn].description("Optional component state to perform a selective key removal. If omitted, clears all state."))) .errorOut(oneOf[Unit]( oneOfVariantSingletonMatcher(sttp.model.StatusCode(400), emptyOutput.description("NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."))(()), @@ -3408,13 +3408,13 @@ object TapirGeneratedEndpoints { .out(jsonBody[ProcessGroupEntity].description("").and(statusCode(sttp.model.StatusCode(201)))) .tags(List("ProcessGroups")) - type UploadProcessGroupEndpoint = Endpoint[Unit, (String, UploadProcessGroupRequest), Unit, ProcessGroupEntity, Any] + type UploadProcessGroupEndpoint = Endpoint[Unit, (String, Option[UploadProcessGroupRequest]), Unit, ProcessGroupEntity, Any] lazy val uploadProcessGroup: UploadProcessGroupEndpoint = endpoint .name("uploadProcessGroup") .post .in(("process-groups" / path[String]("id").description("The process group id.") / "process-groups" / "upload")) - .in(multipartBody[UploadProcessGroupRequest]) + .in(multipartBody[UploadProcessGroupRequest].map(Option(_))(_.orNull)) .errorOut(oneOf[Unit]( oneOfVariantSingletonMatcher(sttp.model.StatusCode(400), emptyOutput.description("NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."))(()), oneOfVariantSingletonMatcher(sttp.model.StatusCode(404), emptyOutput.description("The specified resource could not be found."))(()), @@ -3526,7 +3526,7 @@ object TapirGeneratedEndpoints { .post .in(("processors" / "run-status-details" / "queries")) .in(oneOfBody[GetProcessorRunStatusDetailsBodyIn]( - EndpointIO.Body(RawBodyType.ByteArrayBody, Codec.id[Array[Byte], `*/*CodecFormat`](`*/*CodecFormat`(), Schema.schemaForByteArray), EndpointIO.Info.empty).map(GetProcessorRunStatusDetailsBody0In(_))(_.value).widenBody[GetProcessorRunStatusDetailsBodyIn].description("The request for the processors that should be included in the results"), + EndpointIO.Body(RawBodyType.ByteArrayBody, Codec.id[Array[Byte], `*/*CodecFormat`](`*/*CodecFormat`(), Schema.schemaForByteArray), EndpointIO.Info.empty).map(Option(_))(_.orNull).map(GetProcessorRunStatusDetailsBodyOption_Array_Byte__In(_))(_.value).widenBody[GetProcessorRunStatusDetailsBodyIn].description("The request for the processors that should be included in the results"), jsonBody[Option[RunStatusDetailsRequestEntity]].map(GetProcessorRunStatusDetailsBodyOption_RunStatusDetailsRequestEntity_In(_))(_.value).widenBody[GetProcessorRunStatusDetailsBodyIn].description("The request for the processors that should be included in the results"))) .errorOut(oneOf[Unit]( oneOfVariantSingletonMatcher(sttp.model.StatusCode(400), emptyOutput.description("NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."))(()), @@ -3719,7 +3719,7 @@ object TapirGeneratedEndpoints { .post .in(("processors" / path[String]("id").description("The processor id.") / "state" / "clear-requests")) .in(oneOfBody[ClearState_3BodyIn]( - EndpointIO.Body(RawBodyType.ByteArrayBody, Codec.id[Array[Byte], `*/*CodecFormat`](`*/*CodecFormat`(), Schema.schemaForByteArray), EndpointIO.Info.empty).map(ClearState_3Body0In(_))(_.value).widenBody[ClearState_3BodyIn].description("Optional component state to perform a selective key removal. If omitted, clears all state."), + EndpointIO.Body(RawBodyType.ByteArrayBody, Codec.id[Array[Byte], `*/*CodecFormat`](`*/*CodecFormat`(), Schema.schemaForByteArray), EndpointIO.Info.empty).map(Option(_))(_.orNull).map(ClearState_3BodyOption_Array_Byte__In(_))(_.value).widenBody[ClearState_3BodyIn].description("Optional component state to perform a selective key removal. If omitted, clears all state."), jsonBody[Option[ComponentStateEntity]].map(ClearState_3BodyOption_ComponentStateEntity_In(_))(_.value).widenBody[ClearState_3BodyIn].description("Optional component state to perform a selective key removal. If omitted, clears all state."))) .errorOut(oneOf[Unit]( oneOfVariantSingletonMatcher(sttp.model.StatusCode(400), emptyOutput.description("NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."))(()), @@ -3841,7 +3841,7 @@ object TapirGeneratedEndpoints { oneOfVariantSingletonMatcher(sttp.model.StatusCode(401), emptyOutput.description("Client could not be authenticated."))(()))) .out(oneOf[(Option[sttp.capabilities.pekko.PekkoStreams.BinaryStream], GetInputContentResponseHeader)]( oneOfVariantValueMatcher(sttp.model.StatusCode(206), emptyOutputAs(None).description("Partial Content with range of bytes requested").and(emptyOutputAs(GetInputContentResponseHeader206))){ case (None, GetInputContentResponseHeader206) => true}, - oneOfVariantValueMatcher(sttp.model.StatusCode(200), streamBody(sttp.capabilities.pekko.PekkoStreams)(Schema.binary[StreamingOutput], `*/*CodecFormat`()).description("").toEndpointIO.map(Some(_))(_.orNull).and(header[String]("Content-Type").validate(Validator.pattern("([^*]+|[*])/([^*]+|[*])")).map(GetInputContentResponseHeader200(_))(_.`Content-Type`))){ case (Some(_: sttp.capabilities.pekko.PekkoStreams.BinaryStream), (_: GetInputContentResponseHeader200)) => true })) + oneOfVariantValueMatcher(sttp.model.StatusCode(200), streamBody(sttp.capabilities.pekko.PekkoStreams)(Schema.binary[StreamingOutput], `*/*CodecFormat`()).description("").toEndpointIO.map(Some(_))(_.orNull).and(header[String]("Content-Type").validate(Validator.pattern("([^*]+|[*])/([^*]+|[*])")).map(GetInputContentResponseHeader200(_))(_.`Content-Type`))){ case (Some(_: sttp.capabilities.pekko.PekkoStreams.BinaryStream @scala.unchecked), (_: GetInputContentResponseHeader200)) => true })) .tags(List("ProvenanceEvents")) type GetOutputContentEndpoint = Endpoint[Unit, (LongParameterInt, Option[String], Option[String]), Unit, (Option[sttp.capabilities.pekko.PekkoStreams.BinaryStream], GetOutputContentResponseHeader), sttp.capabilities.pekko.PekkoStreams] @@ -3861,7 +3861,7 @@ object TapirGeneratedEndpoints { oneOfVariantSingletonMatcher(sttp.model.StatusCode(401), emptyOutput.description("Client could not be authenticated."))(()))) .out(oneOf[(Option[sttp.capabilities.pekko.PekkoStreams.BinaryStream], GetOutputContentResponseHeader)]( oneOfVariantValueMatcher(sttp.model.StatusCode(206), emptyOutputAs(None).description("Partial Content with range of bytes requested").and(emptyOutputAs(GetOutputContentResponseHeader206))){ case (None, GetOutputContentResponseHeader206) => true}, - oneOfVariantValueMatcher(sttp.model.StatusCode(200), streamBody(sttp.capabilities.pekko.PekkoStreams)(Schema.binary[StreamingOutput], `*/*CodecFormat`()).description("").toEndpointIO.map(Some(_))(_.orNull).and(header[String]("Content-Type").validate(Validator.pattern("([^*]+|[*])/([^*]+|[*])")).map(GetOutputContentResponseHeader200(_))(_.`Content-Type`))){ case (Some(_: sttp.capabilities.pekko.PekkoStreams.BinaryStream), (_: GetOutputContentResponseHeader200)) => true })) + oneOfVariantValueMatcher(sttp.model.StatusCode(200), streamBody(sttp.capabilities.pekko.PekkoStreams)(Schema.binary[StreamingOutput], `*/*CodecFormat`()).description("").toEndpointIO.map(Some(_))(_.orNull).and(header[String]("Content-Type").validate(Validator.pattern("([^*]+|[*])/([^*]+|[*])")).map(GetOutputContentResponseHeader200(_))(_.`Content-Type`))){ case (Some(_: sttp.capabilities.pekko.PekkoStreams.BinaryStream @scala.unchecked), (_: GetOutputContentResponseHeader200)) => true })) .tags(List("ProvenanceEvents")) type SubmitLineageRequestEndpoint = Endpoint[Unit, LineageEntity, Unit, LineageEntity, Any] @@ -4286,7 +4286,7 @@ object TapirGeneratedEndpoints { .post .in(("reporting-tasks" / path[String]("id").description("The reporting task id.") / "state" / "clear-requests")) .in(oneOfBody[ClearState_4BodyIn]( - EndpointIO.Body(RawBodyType.ByteArrayBody, Codec.id[Array[Byte], `*/*CodecFormat`](`*/*CodecFormat`(), Schema.schemaForByteArray), EndpointIO.Info.empty).map(ClearState_4Body0In(_))(_.value).widenBody[ClearState_4BodyIn].description("Optional component state to perform a selective key removal. If omitted, clears all state."), + EndpointIO.Body(RawBodyType.ByteArrayBody, Codec.id[Array[Byte], `*/*CodecFormat`](`*/*CodecFormat`(), Schema.schemaForByteArray), EndpointIO.Info.empty).map(Option(_))(_.orNull).map(ClearState_4BodyOption_Array_Byte__In(_))(_.value).widenBody[ClearState_4BodyIn].description("Optional component state to perform a selective key removal. If omitted, clears all state."), jsonBody[Option[ComponentStateEntity]].map(ClearState_4BodyOption_ComponentStateEntity_In(_))(_.value).widenBody[ClearState_4BodyIn].description("Optional component state to perform a selective key removal. If omitted, clears all state."))) .errorOut(oneOf[Unit]( oneOfVariantSingletonMatcher(sttp.model.StatusCode(400), emptyOutput.description("NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."))(()), diff --git a/openapi-codegen/sbt-plugin/src/sbt-test/sbt-openapi-codegen/nifi_test/ExpectedModelPackage.scala.txt b/openapi-codegen/sbt-plugin/src/sbt-test/sbt-openapi-codegen/nifi_test/ExpectedModelPackage.scala.txt index 3cdb709cb3..85eec71ebf 100644 --- a/openapi-codegen/sbt-plugin/src/sbt-test/sbt-openapi-codegen/nifi_test/ExpectedModelPackage.scala.txt +++ b/openapi-codegen/sbt-plugin/src/sbt-test/sbt-openapi-codegen/nifi_test/ExpectedModelPackage.scala.txt @@ -43,12 +43,12 @@ package object models { ) sealed trait ClearState_1BodyIn extends Product with java.io.Serializable - case class ClearState_1Body0In(value: Array[Byte]) extends ClearState_1BodyIn + case class ClearState_1BodyOption_Array_Byte__In(value: Option[Array[Byte]]) extends ClearState_1BodyIn case class ClearState_1BodyOption_ComponentStateEntity_In(value: Option[ComponentStateEntity]) extends ClearState_1BodyIn sealed trait ClearStateBodyIn extends Product with java.io.Serializable - case class ClearStateBody0In(value: Array[Byte]) extends ClearStateBodyIn + case class ClearStateBodyOption_Array_Byte__In(value: Option[Array[Byte]]) extends ClearStateBodyIn case class ClearStateBodyOption_ComponentStateEntity_In(value: Option[ComponentStateEntity]) extends ClearStateBodyIn @@ -66,7 +66,7 @@ package object models { sealed trait ClearState_2BodyIn extends Product with java.io.Serializable - case class ClearState_2Body0In(value: Array[Byte]) extends ClearState_2BodyIn + case class ClearState_2BodyOption_Array_Byte__In(value: Option[Array[Byte]]) extends ClearState_2BodyIn case class ClearState_2BodyOption_ComponentStateEntity_In(value: Option[ComponentStateEntity]) extends ClearState_2BodyIn case class UploadProcessGroupRequest ( @@ -79,12 +79,12 @@ package object models { ) sealed trait GetProcessorRunStatusDetailsBodyIn extends Product with java.io.Serializable - case class GetProcessorRunStatusDetailsBody0In(value: Array[Byte]) extends GetProcessorRunStatusDetailsBodyIn + case class GetProcessorRunStatusDetailsBodyOption_Array_Byte__In(value: Option[Array[Byte]]) extends GetProcessorRunStatusDetailsBodyIn case class GetProcessorRunStatusDetailsBodyOption_RunStatusDetailsRequestEntity_In(value: Option[RunStatusDetailsRequestEntity]) extends GetProcessorRunStatusDetailsBodyIn sealed trait ClearState_3BodyIn extends Product with java.io.Serializable - case class ClearState_3Body0In(value: Array[Byte]) extends ClearState_3BodyIn + case class ClearState_3BodyOption_Array_Byte__In(value: Option[Array[Byte]]) extends ClearState_3BodyIn case class ClearState_3BodyOption_ComponentStateEntity_In(value: Option[ComponentStateEntity]) extends ClearState_3BodyIn sealed trait GetInputContentResponseHeader @@ -97,6 +97,6 @@ package object models { sealed trait ClearState_4BodyIn extends Product with java.io.Serializable - case class ClearState_4Body0In(value: Array[Byte]) extends ClearState_4BodyIn + case class ClearState_4BodyOption_Array_Byte__In(value: Option[Array[Byte]]) extends ClearState_4BodyIn case class ClearState_4BodyOption_ComponentStateEntity_In(value: Option[ComponentStateEntity]) extends ClearState_4BodyIn } diff --git a/openapi-codegen/sbt-plugin/src/sbt-test/sbt-openapi-codegen/oneOf-json-roundtrip-zio/Expected.scala.txt b/openapi-codegen/sbt-plugin/src/sbt-test/sbt-openapi-codegen/oneOf-json-roundtrip-zio/Expected.scala.txt index e682fc26fb..9b15f5170a 100644 --- a/openapi-codegen/sbt-plugin/src/sbt-test/sbt-openapi-codegen/oneOf-json-roundtrip-zio/Expected.scala.txt +++ b/openapi-codegen/sbt-plugin/src/sbt-test/sbt-openapi-codegen/oneOf-json-roundtrip-zio/Expected.scala.txt @@ -167,12 +167,12 @@ object TapirGeneratedEndpoints { .in(jsonBody[ADTWithDiscriminatorNoMapping].description("Update an existent user in the store")) .out(jsonBody[ADTWithDiscriminator].description("successful operation")) - type PutInlineSimpleObjectEndpoint = Endpoint[Unit, PutInlineSimpleObjectRequest, (Array[Byte], PutInlineSimpleObjectResponseErrCode), PutInlineSimpleObjectResponse, Any] + type PutInlineSimpleObjectEndpoint = Endpoint[Unit, Option[PutInlineSimpleObjectRequest], (Array[Byte], PutInlineSimpleObjectResponseErrCode), PutInlineSimpleObjectResponse, Any] lazy val putInlineSimpleObject: PutInlineSimpleObjectEndpoint = endpoint .put .in(("inline" / "simple" / "object")) - .in(multipartBody[PutInlineSimpleObjectRequest]) + .in(multipartBody[PutInlineSimpleObjectRequest].map(Option(_))(_.orNull)) .errorOut(oneOf[(Array[Byte], PutInlineSimpleObjectResponseErrCode)]( oneOfVariantValueMatcher(sttp.model.StatusCode(400), rawBinaryBody(sttp.tapir.RawBodyType.ByteArrayBody).description("application/octet-stream in error position").and(emptyOutputAs(StatusCodeDisambig400))){ case (_: Array[Byte], StatusCodeDisambig400) => true }, oneOfVariantValueMatcher(sttp.model.StatusCode(401), rawBinaryBody(sttp.tapir.RawBodyType.ByteArrayBody).description("application/octet-stream in error position 2").and(emptyOutputAs(StatusCodeDisambig401))){ case (_: Array[Byte], StatusCodeDisambig401) => true })) diff --git a/openapi-codegen/sbt-plugin/src/sbt-test/sbt-openapi-codegen/oneOf-json-roundtrip/Expected.scala.txt b/openapi-codegen/sbt-plugin/src/sbt-test/sbt-openapi-codegen/oneOf-json-roundtrip/Expected.scala.txt index 943c3f0215..3349feba9f 100644 --- a/openapi-codegen/sbt-plugin/src/sbt-test/sbt-openapi-codegen/oneOf-json-roundtrip/Expected.scala.txt +++ b/openapi-codegen/sbt-plugin/src/sbt-test/sbt-openapi-codegen/oneOf-json-roundtrip/Expected.scala.txt @@ -13,6 +13,9 @@ object TapirGeneratedEndpoints { import sttp.tapir.generated.TapirGeneratedEndpointsJsonSerdes._ import TapirGeneratedEndpointsSchemas._ + case class `application/pdfCodecFormat`() extends CodecFormat { + override val mediaType: sttp.model.MediaType = sttp.model.MediaType.unsafeApply(mainType = "application", subType = "pdf") + } case class `application/zipCodecFormat`() extends CodecFormat { override val mediaType: sttp.model.MediaType = sttp.model.MediaType.unsafeApply(mainType = "application", subType = "zip") } @@ -236,25 +239,33 @@ object TapirGeneratedEndpoints { sealed trait GetOneofErrorSecParamTestResponseErrCode extends StatusCodeDisambig sealed trait PostJsonStringJsonBodyBodyIn extends Product with java.io.Serializable - case class PostJsonStringJsonBodyBody1In(value: Array[Byte]) extends PostJsonStringJsonBodyBodyIn + case class PostJsonStringJsonBodyBodyOption_Array_Byte__In(value: Option[Array[Byte]]) extends PostJsonStringJsonBodyBodyIn case class PostJsonStringJsonBodyBodyOption_String_In(value: Option[String]) extends PostJsonStringJsonBodyBodyIn sealed trait PostJsonStringJsonBodyBodyOut extends Product with java.io.Serializable { def `application/json`: () => Option[String] = () => throw new RuntimeException("Body for content type application/json not provided") - def `application/zip`: () => Array[Byte] = () => throw new RuntimeException("Body for content type application/zip not provided") + def `application/zip`: () => Option[Array[Byte]] = () => throw new RuntimeException("Body for content type application/zip not provided") } case class PostJsonStringJsonBodyBodyOutFull ( override val `application/json`: () => Option[String], - override val `application/zip`: () => Array[Byte], + override val `application/zip`: () => Option[Array[Byte]], ) extends PostJsonStringJsonBodyBodyOut - case class PostJsonStringJsonBodyBody1Out(value: Array[Byte]) extends PostJsonStringJsonBodyBodyOut{ - override def `application/zip`: () => Array[Byte] = () => value + case class PostJsonStringJsonBodyBodyOption_Array_Byte__Out(value: Option[Array[Byte]]) extends PostJsonStringJsonBodyBodyOut{ + override def `application/zip`: () => Option[Array[Byte]] = () => value } case class PostJsonStringJsonBodyBodyOption_String_Out(value: Option[String]) extends PostJsonStringJsonBodyBodyOut{ override def `application/json`: () => Option[String] = () => value } + sealed trait PutHeadersTestsResponseHeader + case class PutHeadersTestsResponseHeader200(`Content-Disposition`: String) extends PutHeadersTestsResponseHeader + case object PutHeadersTestsResponseHeader204 extends PutHeadersTestsResponseHeader + + sealed trait PostHeadersTestsResponseHeader + case class PostHeadersTestsResponseHeader200(`Content-Disposition`: String) extends PostHeadersTestsResponseHeader + case object PostHeadersTestsResponseHeader204 extends PostHeadersTestsResponseHeader + case class GetForceEagerRequest ( foo: String, bar: Option[java.util.UUID] = None @@ -563,13 +574,13 @@ object TapirGeneratedEndpoints { .securityIn(auth.bearer[Option[String]]()) .in(oneOfBody[PostJsonStringJsonBodyBodyIn]( stringJsonBody.map(Option(_))(_.orNull).map(PostJsonStringJsonBodyBodyOption_String_In(_))(_.value).widenBody[PostJsonStringJsonBodyBodyIn], - EndpointIO.Body(RawBodyType.ByteArrayBody, Codec.id[Array[Byte], `application/zipCodecFormat`](`application/zipCodecFormat`(), Schema.schemaForByteArray), EndpointIO.Info.empty).map(PostJsonStringJsonBodyBody1In(_))(_.value).widenBody[PostJsonStringJsonBodyBodyIn])) + EndpointIO.Body(RawBodyType.ByteArrayBody, Codec.id[Array[Byte], `application/zipCodecFormat`](`application/zipCodecFormat`(), Schema.schemaForByteArray), EndpointIO.Info.empty).map(Option(_))(_.orNull).map(PostJsonStringJsonBodyBodyOption_Array_Byte__In(_))(_.value).widenBody[PostJsonStringJsonBodyBodyIn])) .out(oneOf[Option[PostJsonStringJsonBodyBodyOut]]( oneOfVariantValueMatcher(sttp.model.StatusCode(200), oneOfBody[PostJsonStringJsonBodyBodyOut]( stringJsonBody.map(Option(_))(_.orNull).map(PostJsonStringJsonBodyBodyOption_String_Out(_))(_.`application/json`()) .map(_.asInstanceOf[PostJsonStringJsonBodyBodyOut])(p => PostJsonStringJsonBodyBodyOption_String_Out(p.`application/json`())).description("Possibly-invalid json"), - EndpointIO.Body(RawBodyType.ByteArrayBody, Codec.id[Array[Byte], `application/zipCodecFormat`](`application/zipCodecFormat`(), Schema.schemaForByteArray), EndpointIO.Info.empty).map(PostJsonStringJsonBodyBody1Out(_))(_.`application/zip`()) - .map(_.asInstanceOf[PostJsonStringJsonBodyBodyOut])(p => PostJsonStringJsonBodyBody1Out(p.`application/zip`())).description("Possibly-invalid json")).map(Some(_))(_.orNull)){ case Some(_: PostJsonStringJsonBodyBodyOut) => true }, + EndpointIO.Body(RawBodyType.ByteArrayBody, Codec.id[Array[Byte], `application/zipCodecFormat`](`application/zipCodecFormat`(), Schema.schemaForByteArray), EndpointIO.Info.empty).map(Option(_))(_.orNull).map(PostJsonStringJsonBodyBodyOption_Array_Byte__Out(_))(_.`application/zip`()) + .map(_.asInstanceOf[PostJsonStringJsonBodyBodyOut])(p => PostJsonStringJsonBodyBodyOption_Array_Byte__Out(p.`application/zip`())).description("Possibly-invalid json")).map(Some(_))(_.orNull)){ case Some(_: PostJsonStringJsonBodyBodyOut) => true }, oneOfVariantSingletonMatcher(sttp.model.StatusCode(204), emptyOutput.description("No response"))(None))) .attribute[TapirCodegenDirectivesExtension](tapirCodegenDirectivesExtensionKey, Vector("json-body-as-string")) @@ -601,7 +612,28 @@ object TapirGeneratedEndpoints { .in(("headers" / "tests")) .securityIn(auth.oauth2.implicitFlow("https://petstore3.swagger.io/oauth/authorize", None)) .out(statusCode(sttp.model.StatusCode(302)).description("response").and(header[Option[String]]("foo").description("a description"))) + + type PutHeadersTestsEndpoint = Endpoint[String, Unit, Unit, (Option[String], PutHeadersTestsResponseHeader), Any] + lazy val putHeadersTests: PutHeadersTestsEndpoint = + endpoint + .put + .in(("headers" / "tests")) + .securityIn(auth.oauth2.implicitFlow("https://petstore3.swagger.io/oauth/authorize", None)) + .out(oneOf[(Option[String], PutHeadersTestsResponseHeader)]( + oneOfVariantValueMatcher(sttp.model.StatusCode(200), stringBody.map(Option(_))(_.orNull).description("Strict text response with a header").and(header[String]("Content-Disposition").description("").map(PutHeadersTestsResponseHeader200(_))(_.`Content-Disposition`))){ case (Some(_: String), (_: PutHeadersTestsResponseHeader200)) => true }, + oneOfVariantValueMatcher(sttp.model.StatusCode(204), emptyOutputAs(None).description("Nothing to do").and(emptyOutputAs(PutHeadersTestsResponseHeader204))){ case (None, PutHeadersTestsResponseHeader204) => true})) + type PostHeadersTestsEndpoint = Endpoint[String, Unit, Unit, (Option[Array[Byte]], PostHeadersTestsResponseHeader), Any] + lazy val postHeadersTests: PostHeadersTestsEndpoint = + endpoint + .post + .in(("headers" / "tests")) + .securityIn(auth.oauth2.implicitFlow("https://petstore3.swagger.io/oauth/authorize", None)) + .out(oneOf[(Option[Array[Byte]], PostHeadersTestsResponseHeader)]( + oneOfVariantValueMatcher(sttp.model.StatusCode(200), EndpointIO.Body(RawBodyType.ByteArrayBody, Codec.id[Array[Byte], `application/pdfCodecFormat`](`application/pdfCodecFormat`(), Schema.schemaForByteArray), EndpointIO.Info.empty).map(Option(_))(_.orNull).description("Strict bin response with a header").and(header[String]("Content-Disposition").description("").map(PostHeadersTestsResponseHeader200(_))(_.`Content-Disposition`))){ case (Some(_: Array[Byte]), (_: PostHeadersTestsResponseHeader200)) => true }, + oneOfVariantValueMatcher(sttp.model.StatusCode(204), emptyOutputAs(None).description("Nothing to do").and(emptyOutputAs(PostHeadersTestsResponseHeader204))){ case (None, PostHeadersTestsResponseHeader204) => true})) + .attribute[TapirCodegenDirectivesExtension](tapirCodegenDirectivesExtensionKey, Vector("force-resp-body-eager")) + type GetPatternRestrictedObjectEndpoint = Endpoint[String, (String, Option[String], String, Option[String], String), Unit, String, Any] lazy val getPatternRestrictedObject: GetPatternRestrictedObjectEndpoint = endpoint @@ -619,7 +651,7 @@ object TapirGeneratedEndpoints { }.getOrElse(ValidationResult.Valid)))) .in(header[String]("h2").validate(Validator.minLength(3))) .out(jsonBody[String].description("ok")) - + type GetForceEagerEndpoint = Endpoint[String, sttp.capabilities.pekko.PekkoStreams.BinaryStream, Unit, String, sttp.capabilities.pekko.PekkoStreams] lazy val getForceEager: GetForceEagerEndpoint = endpoint @@ -630,13 +662,13 @@ object TapirGeneratedEndpoints { .out(stringBodyUtf8AnyFormat(Codec.id[String, `text/csvCodecFormat`](`text/csvCodecFormat`(), Schema.schemaForString)).description("An object")) .attribute[TapirCodegenDirectivesExtension](tapirCodegenDirectivesExtensionKey, Vector("force-resp-body-eager")) - type PutForceEagerEndpoint = Endpoint[String, String, Unit, String, Any] + type PutForceEagerEndpoint = Endpoint[String, Option[String], Unit, String, Any] lazy val putForceEager: PutForceEagerEndpoint = endpoint .put .in(("force" / "eager")) .securityIn(auth.oauth2.implicitFlow("https://petstore3.swagger.io/oauth/authorize", None)) - .in(stringBodyUtf8AnyFormat(Codec.id[String, `text/csvCodecFormat`](`text/csvCodecFormat`(), Schema.schemaForString))) + .in(stringBodyUtf8AnyFormat(Codec.id[String, `text/csvCodecFormat`](`text/csvCodecFormat`(), Schema.schemaForString)).map(Option(_))(_.orNull)) .out(stringBodyUtf8AnyFormat(Codec.id[String, `text/csvCodecFormat`](`text/csvCodecFormat`(), Schema.schemaForString)).description("An object")) .attribute[TapirCodegenDirectivesExtension](tapirCodegenDirectivesExtensionKey, Vector("force-eager")) @@ -650,13 +682,13 @@ object TapirGeneratedEndpoints { .out(jsonBody[PostForceEagerResponse].description("An object")) .attribute[TapirCodegenDirectivesExtension](tapirCodegenDirectivesExtensionKey, Vector("force-eager")) - type PatchForceEagerEndpoint = Endpoint[String, String, Unit, sttp.capabilities.pekko.PekkoStreams.BinaryStream, sttp.capabilities.pekko.PekkoStreams] + type PatchForceEagerEndpoint = Endpoint[String, Option[String], Unit, sttp.capabilities.pekko.PekkoStreams.BinaryStream, sttp.capabilities.pekko.PekkoStreams] lazy val patchForceEager: PatchForceEagerEndpoint = endpoint .patch .in(("force" / "eager")) .securityIn(auth.oauth2.implicitFlow("https://petstore3.swagger.io/oauth/authorize", None)) - .in(stringBodyUtf8AnyFormat(Codec.id[String, `text/csvCodecFormat`](`text/csvCodecFormat`(), Schema.schemaForString))) + .in(stringBodyUtf8AnyFormat(Codec.id[String, `text/csvCodecFormat`](`text/csvCodecFormat`(), Schema.schemaForString)).map(Option(_))(_.orNull)) .out(streamBody(sttp.capabilities.pekko.PekkoStreams)(Schema.binary[PatchForceEagerResponse], `text/csvCodecFormat`()).description("An object")) .attribute[TapirCodegenDirectivesExtension](tapirCodegenDirectivesExtensionKey, Vector("force-req-body-eager")) @@ -713,13 +745,13 @@ object TapirGeneratedEndpoints { .map(_.asInstanceOf[PatchForceStreamingBodyOut])(p => PatchForceStreamingBody1Out(p.`text/plain`())).description("An object")))) .attribute[TapirCodegenDirectivesExtension](tapirCodegenDirectivesExtensionKey, Vector("force-resp-body-streaming")) - type PutInlineSimpleObjectEndpoint = Endpoint[String, PutInlineSimpleObjectRequest, (Array[Byte], PutInlineSimpleObjectResponseErrCode), PutInlineSimpleObjectResponse, Any] + type PutInlineSimpleObjectEndpoint = Endpoint[String, Option[PutInlineSimpleObjectRequest], (Array[Byte], PutInlineSimpleObjectResponseErrCode), PutInlineSimpleObjectResponse, Any] lazy val putInlineSimpleObject: PutInlineSimpleObjectEndpoint = endpoint .put .in(("inline" / "simple" / "object")) .securityIn(auth.oauth2.implicitFlow("https://petstore3.swagger.io/oauth/authorize", None)) - .in(multipartBody[PutInlineSimpleObjectRequest]) + .in(multipartBody[PutInlineSimpleObjectRequest].map(Option(_))(_.orNull)) .errorOut(oneOf[(Array[Byte], PutInlineSimpleObjectResponseErrCode)]( oneOfVariantValueMatcher(sttp.model.StatusCode(400), rawBinaryBody(sttp.tapir.RawBodyType.ByteArrayBody).description("application/octet-stream in error position").and(emptyOutputAs(StatusCodeDisambig400))){ case (_: Array[Byte], StatusCodeDisambig400) => true }, oneOfVariantValueMatcher(sttp.model.StatusCode(401), rawBinaryBody(sttp.tapir.RawBodyType.ByteArrayBody).description("application/octet-stream in error position 2").and(emptyOutputAs(StatusCodeDisambig401))){ case (_: Array[Byte], StatusCodeDisambig401) => true })) @@ -784,7 +816,7 @@ object TapirGeneratedEndpoints { .out(jsonBody[Aardvark].description("OK")) - lazy val generatedEndpoints = List(getSecurityGroupSecurityGroupName, getSecurityGroupSecurityGroupNameMorePath, getBinaryTest, postBinaryTest, putOptionalTest, postOptionalTest, putAdtTest, postAdtTest, postInlineEnumTest, getOneofErrorSecParamTest, postJsonStringJsonBody, postJsonStringJsonBodySimple, getOneofOptionTest, getHeadersTests, getPatternRestrictedObject, getForceEager, putForceEager, postForceEager, patchForceEager, getForceStreaming, putForceStreaming, postForceStreaming, patchForceStreaming, putInlineSimpleObject, postInlineSimpleObject, deleteInlineSimpleObject, patchInlineSimpleObject, postAllOf, postValidationTest, postWrappedOneOf) + lazy val generatedEndpoints = List(getSecurityGroupSecurityGroupName, getSecurityGroupSecurityGroupNameMorePath, getBinaryTest, postBinaryTest, putOptionalTest, postOptionalTest, putAdtTest, postAdtTest, postInlineEnumTest, getOneofErrorSecParamTest, postJsonStringJsonBody, postJsonStringJsonBodySimple, getOneofOptionTest, getHeadersTests, putHeadersTests, postHeadersTests, getPatternRestrictedObject, getForceEager, putForceEager, postForceEager, patchForceEager, getForceStreaming, putForceStreaming, postForceStreaming, patchForceStreaming, putInlineSimpleObject, postInlineSimpleObject, deleteInlineSimpleObject, patchInlineSimpleObject, postAllOf, postValidationTest, postWrappedOneOf) object Servers { diff --git a/openapi-codegen/sbt-plugin/src/sbt-test/sbt-openapi-codegen/oneOf-json-roundtrip/swagger.yaml b/openapi-codegen/sbt-plugin/src/sbt-test/sbt-openapi-codegen/oneOf-json-roundtrip/swagger.yaml index 8df35e0b47..3dd1285d9f 100644 --- a/openapi-codegen/sbt-plugin/src/sbt-test/sbt-openapi-codegen/oneOf-json-roundtrip/swagger.yaml +++ b/openapi-codegen/sbt-plugin/src/sbt-test/sbt-openapi-codegen/oneOf-json-roundtrip/swagger.yaml @@ -339,6 +339,22 @@ paths: description: a description schema: type: string + post: + description: test headers with optional strict bin response body + x-tapir-codegen-directives: + - force-resp-body-eager + responses: + "200": + $ref: '#/components/responses/BinResponseWithHeader' + "204": + description: Nothing to do + put: + description: test headers with optional strict text response body + responses: + "200": + $ref: '#/components/responses/TextResponseWithHeader' + "204": + description: Nothing to do '/pattern/{restricted}/object': parameters: @@ -789,7 +805,7 @@ paths: '/wrapped-one-of': post: - security: [] + security: [ ] requestBody: content: application/json: @@ -824,6 +840,32 @@ components: application/json: schema: $ref: '#/components/schemas/NotNullableThingy' + BinResponseWithHeader: + description: Strict bin response with a header + headers: + Content-Disposition: + description: "" + required: true + schema: + type: string + content: + application/pdf: + schema: + type: string + format: binary + TextResponseWithHeader: + description: Strict text response with a header + headers: + Content-Disposition: + description: "" + required: true + schema: + type: string + content: + text/plain: + schema: + type: string + format: binary requestBodies: optionalityTest: required: false diff --git a/openapi-codegen/sbt-plugin/src/sbt-test/sbt-openapi-codegen/oneOf-json-roundtrip_jsoniter/Expected.scala.txt b/openapi-codegen/sbt-plugin/src/sbt-test/sbt-openapi-codegen/oneOf-json-roundtrip_jsoniter/Expected.scala.txt index ec10eb154d..b0988c5929 100644 --- a/openapi-codegen/sbt-plugin/src/sbt-test/sbt-openapi-codegen/oneOf-json-roundtrip_jsoniter/Expected.scala.txt +++ b/openapi-codegen/sbt-plugin/src/sbt-test/sbt-openapi-codegen/oneOf-json-roundtrip_jsoniter/Expected.scala.txt @@ -363,12 +363,12 @@ object TapirGeneratedEndpoints { .in(xmlBody[Pet]) .out(xmlBody[Pet].description("An object")) - type PutInlineSimpleObjectEndpoint = Endpoint[Unit, PutInlineSimpleObjectRequest, (Array[Byte], PutInlineSimpleObjectResponseErrCode), PutInlineSimpleObjectResponse, Any] + type PutInlineSimpleObjectEndpoint = Endpoint[Unit, Option[PutInlineSimpleObjectRequest], (Array[Byte], PutInlineSimpleObjectResponseErrCode), PutInlineSimpleObjectResponse, Any] lazy val putInlineSimpleObject: PutInlineSimpleObjectEndpoint = endpoint .put .in(("inline" / "simple" / "object")) - .in(multipartBody[PutInlineSimpleObjectRequest]) + .in(multipartBody[PutInlineSimpleObjectRequest].map(Option(_))(_.orNull)) .errorOut(oneOf[(Array[Byte], PutInlineSimpleObjectResponseErrCode)]( oneOfVariantValueMatcher(sttp.model.StatusCode(400), rawBinaryBody(sttp.tapir.RawBodyType.ByteArrayBody).description("application/octet-stream in error position").and(emptyOutputAs(StatusCodeDisambig400))){ case (_: Array[Byte], StatusCodeDisambig400) => true }, oneOfVariantValueMatcher(sttp.model.StatusCode(401), rawBinaryBody(sttp.tapir.RawBodyType.ByteArrayBody).description("application/octet-stream in error position 2").and(emptyOutputAs(StatusCodeDisambig401))){ case (_: Array[Byte], StatusCodeDisambig401) => true })) diff --git a/openapi-codegen/sbt-plugin/src/sbt-test/sbt-openapi-codegen/oneOf-json-roundtrip_scala3/Expected.scala.txt b/openapi-codegen/sbt-plugin/src/sbt-test/sbt-openapi-codegen/oneOf-json-roundtrip_scala3/Expected.scala.txt index 18291e4e1b..64e82a3a00 100644 --- a/openapi-codegen/sbt-plugin/src/sbt-test/sbt-openapi-codegen/oneOf-json-roundtrip_scala3/Expected.scala.txt +++ b/openapi-codegen/sbt-plugin/src/sbt-test/sbt-openapi-codegen/oneOf-json-roundtrip_scala3/Expected.scala.txt @@ -257,7 +257,7 @@ object TapirGeneratedEndpoints { oneOfVariantValueMatcher(sttp.model.StatusCode(200), emptyOutput.description("empty response 1").and(emptyOutputAs(StatusCodeDisambig200))){ case StatusCodeDisambig200 => true}, oneOfVariantValueMatcher(sttp.model.StatusCode(201), emptyOutput.description("empty response 2").and(emptyOutputAs(StatusCodeDisambig201))){ case StatusCodeDisambig201 => true})) - type PutInlineSimpleObjectEndpoint = Endpoint[Basic_or_Bearer_SecurityIn, PutInlineSimpleObjectRequest, (Array[Byte], PutInlineSimpleObjectResponseErrCode), PutInlineSimpleObjectResponse, Any] + type PutInlineSimpleObjectEndpoint = Endpoint[Basic_or_Bearer_SecurityIn, Option[PutInlineSimpleObjectRequest], (Array[Byte], PutInlineSimpleObjectResponseErrCode), PutInlineSimpleObjectResponse, Any] lazy val putInlineSimpleObject: PutInlineSimpleObjectEndpoint = endpoint .put @@ -274,7 +274,7 @@ object TapirGeneratedEndpoints { case BasicSecurityIn(x) => (Some(x), None) case BearerSecurityIn(x) => (None, Some(x)) } - .in(multipartBody[PutInlineSimpleObjectRequest]) + .in(multipartBody[PutInlineSimpleObjectRequest].map(Option(_))(_.orNull)) .errorOut(oneOf[(Array[Byte], PutInlineSimpleObjectResponseErrCode)]( oneOfVariantValueMatcher(sttp.model.StatusCode(400), rawBinaryBody(sttp.tapir.RawBodyType.ByteArrayBody).description("application/octet-stream in error position").and(emptyOutputAs(StatusCodeDisambig400))){ case (_: Array[Byte], StatusCodeDisambig400) => true }, oneOfVariantValueMatcher(sttp.model.StatusCode(401), rawBinaryBody(sttp.tapir.RawBodyType.ByteArrayBody).description("application/octet-stream in error position 2").and(emptyOutputAs(StatusCodeDisambig401))){ case (_: Array[Byte], StatusCodeDisambig401) => true })) diff --git a/openapi-codegen/sbt-plugin/src/sbt-test/sbt-openapi-codegen/petstore/Expected.scala.txt b/openapi-codegen/sbt-plugin/src/sbt-test/sbt-openapi-codegen/petstore/Expected.scala.txt index a31b77a5a2..4ebfeb4687 100644 --- a/openapi-codegen/sbt-plugin/src/sbt-test/sbt-openapi-codegen/petstore/Expected.scala.txt +++ b/openapi-codegen/sbt-plugin/src/sbt-test/sbt-openapi-codegen/petstore/Expected.scala.txt @@ -169,7 +169,7 @@ object TapirGeneratedEndpoints { sealed trait UploadFileResponseErrCode extends StatusCodeDisambig sealed trait PlaceOrderBodyIn extends Product with java.io.Serializable - case class PlaceOrderBody2In(value: Array[Byte]) extends PlaceOrderBodyIn + case class PlaceOrderBodyOption_Array_Byte__In(value: Option[Array[Byte]]) extends PlaceOrderBodyIn case class PlaceOrderBodyOption_Order_In(value: Option[Order]) extends PlaceOrderBodyIn sealed trait PlaceOrderResponseErrCode extends StatusCodeDisambig @@ -177,14 +177,14 @@ object TapirGeneratedEndpoints { sealed trait DeleteOrderResponseErrCode extends StatusCodeDisambig sealed trait CreateUserBodyIn extends Product with java.io.Serializable - case class CreateUserBody2In(value: Array[Byte]) extends CreateUserBodyIn + case class CreateUserBodyOption_Array_Byte__In(value: Option[Array[Byte]]) extends CreateUserBodyIn case class CreateUserBodyOption_User_In(value: Option[User]) extends CreateUserBodyIn sealed trait LoginUserResponseErrCode extends StatusCodeDisambig sealed trait GetUserByNameResponseErrCode extends StatusCodeDisambig sealed trait UpdateUserBodyIn extends Product with java.io.Serializable - case class UpdateUserBody2In(value: Array[Byte]) extends UpdateUserBodyIn + case class UpdateUserBodyOption_Array_Byte__In(value: Option[Array[Byte]]) extends UpdateUserBodyIn case class UpdateUserBodyOption_User_In(value: Option[User]) extends UpdateUserBodyIn sealed trait UpdateUserResponseErrCode extends StatusCodeDisambig @@ -415,7 +415,7 @@ object TapirGeneratedEndpoints { .in(oneOfBody[PlaceOrderBodyIn]( jsonBody[Option[Order]].map(PlaceOrderBodyOption_Order_In(_))(_.value).widenBody[PlaceOrderBodyIn], xmlBody[Option[Order]].map(PlaceOrderBodyOption_Order_In(_))(_.value).widenBody[PlaceOrderBodyIn], - EndpointIO.Body(RawBodyType.ByteArrayBody, Codec.id[Array[Byte], `application/x-www-form-urlencodedCodecFormat`](`application/x-www-form-urlencodedCodecFormat`(), Schema.schemaForByteArray), EndpointIO.Info.empty).map(PlaceOrderBody2In(_))(_.value).widenBody[PlaceOrderBodyIn])) + EndpointIO.Body(RawBodyType.ByteArrayBody, Codec.id[Array[Byte], `application/x-www-form-urlencodedCodecFormat`](`application/x-www-form-urlencodedCodecFormat`(), Schema.schemaForByteArray), EndpointIO.Info.empty).map(Option(_))(_.orNull).map(PlaceOrderBodyOption_Array_Byte__In(_))(_.value).widenBody[PlaceOrderBodyIn])) .errorOut(oneOf[PlaceOrderResponseErrCode]( oneOfVariantValueMatcher(sttp.model.StatusCode(400), emptyOutput.description("Invalid input").and(emptyOutputAs(StatusCodeDisambig400))){ case StatusCodeDisambig400 => true}, oneOfVariantValueMatcher(sttp.model.StatusCode(422), emptyOutput.description("Validation exception").and(emptyOutputAs(StatusCodeDisambig422))){ case StatusCodeDisambig422 => true}, @@ -462,7 +462,7 @@ object TapirGeneratedEndpoints { .in(oneOfBody[CreateUserBodyIn]( jsonBody[Option[User]].map(CreateUserBodyOption_User_In(_))(_.value).widenBody[CreateUserBodyIn].description("Created user object"), xmlBody[Option[User]].map(CreateUserBodyOption_User_In(_))(_.value).widenBody[CreateUserBodyIn].description("Created user object"), - EndpointIO.Body(RawBodyType.ByteArrayBody, Codec.id[Array[Byte], `application/x-www-form-urlencodedCodecFormat`](`application/x-www-form-urlencodedCodecFormat`(), Schema.schemaForByteArray), EndpointIO.Info.empty).map(CreateUserBody2In(_))(_.value).widenBody[CreateUserBodyIn].description("Created user object"))) + EndpointIO.Body(RawBodyType.ByteArrayBody, Codec.id[Array[Byte], `application/x-www-form-urlencodedCodecFormat`](`application/x-www-form-urlencodedCodecFormat`(), Schema.schemaForByteArray), EndpointIO.Info.empty).map(Option(_))(_.orNull).map(CreateUserBodyOption_Array_Byte__In(_))(_.value).widenBody[CreateUserBodyIn].description("Created user object"))) .errorOut(statusCode.description("Unexpected error")) .out(oneOfBody[User]( jsonBody[User].description("successful operation"), @@ -533,7 +533,7 @@ object TapirGeneratedEndpoints { .in(oneOfBody[UpdateUserBodyIn]( jsonBody[Option[User]].map(UpdateUserBodyOption_User_In(_))(_.value).widenBody[UpdateUserBodyIn].description("Update an existent user in the store"), xmlBody[Option[User]].map(UpdateUserBodyOption_User_In(_))(_.value).widenBody[UpdateUserBodyIn].description("Update an existent user in the store"), - EndpointIO.Body(RawBodyType.ByteArrayBody, Codec.id[Array[Byte], `application/x-www-form-urlencodedCodecFormat`](`application/x-www-form-urlencodedCodecFormat`(), Schema.schemaForByteArray), EndpointIO.Info.empty).map(UpdateUserBody2In(_))(_.value).widenBody[UpdateUserBodyIn].description("Update an existent user in the store"))) + EndpointIO.Body(RawBodyType.ByteArrayBody, Codec.id[Array[Byte], `application/x-www-form-urlencodedCodecFormat`](`application/x-www-form-urlencodedCodecFormat`(), Schema.schemaForByteArray), EndpointIO.Info.empty).map(Option(_))(_.orNull).map(UpdateUserBodyOption_Array_Byte__In(_))(_.value).widenBody[UpdateUserBodyIn].description("Update an existent user in the store"))) .errorOut(oneOf[UpdateUserResponseErrCode]( oneOfVariantValueMatcher(sttp.model.StatusCode(400), emptyOutput.description("bad request").and(emptyOutputAs(StatusCodeDisambig400))){ case StatusCodeDisambig400 => true}, oneOfVariantValueMatcher(sttp.model.StatusCode(404), emptyOutput.description("user not found").and(emptyOutputAs(StatusCodeDisambig404))){ case StatusCodeDisambig404 => true}, diff --git a/openapi-codegen/sbt-plugin/src/sbt-test/sbt-openapi-codegen/petstore/src/test/scala/XmlRoundtrip.scala b/openapi-codegen/sbt-plugin/src/sbt-test/sbt-openapi-codegen/petstore/src/test/scala/XmlRoundtrip.scala index f6385e9960..94cfb9e280 100644 --- a/openapi-codegen/sbt-plugin/src/sbt-test/sbt-openapi-codegen/petstore/src/test/scala/XmlRoundtrip.scala +++ b/openapi-codegen/sbt-plugin/src/sbt-test/sbt-openapi-codegen/petstore/src/test/scala/XmlRoundtrip.scala @@ -128,10 +128,10 @@ class XmlRoundtrip extends AnyFreeSpec with Matchers { { case PlaceOrderBodyOption_Order_In(Some(o)) => Future successful Right(o) - case PlaceOrderBodyOption_Order_In(None) => Future.successful(Right(Order())) - case PlaceOrderBody2In(bytes) if bytes.isEmpty => Future.successful(Right(Order())) - case PlaceOrderBody2In(bytes) => - val m = new String(bytes, "utf-8").split('&').map(_.split("=", 2)).map { case Array(k, v) => k -> v }.toMap + case PlaceOrderBodyOption_Order_In(None) => Future.successful(Right(Order())) + case PlaceOrderBodyOption_Array_Byte__In(bytes) if bytes.forall(_.isEmpty) => Future.successful(Right(Order())) + case PlaceOrderBodyOption_Array_Byte__In(bytes) => + val m = new String(bytes.get, "utf-8").split('&').map(_.split("=", 2)).map { case Array(k, v) => k -> v }.toMap Future( Order( id = m.get("id").map(_.toLong),