All Downloads are FREE. Search and download functionalities are using the official Maven repository.

sttp.tapir.server.vertx.decoders.VertxRequestBody.scala Maven / Gradle / Ivy

package sttp.tapir.server.vertx.decoders

import io.vertx.core.Future
import io.vertx.ext.web.{FileUpload, RoutingContext}
import sttp.capabilities.Streams
import sttp.model.{MediaType, Part}
import sttp.tapir.model.ServerRequest
import sttp.tapir.{FileRange, InputStreamRange, RawBodyType}
import sttp.tapir.server.interpreter.{RawValue, RequestBody}
import sttp.tapir.server.vertx.VertxServerOptions
import sttp.tapir.server.vertx.interpreters.FromVFuture
import sttp.tapir.server.vertx.streams.ReadStreamCompatible

import java.io.{ByteArrayInputStream, File}
import java.nio.ByteBuffer
import java.nio.charset.Charset
import java.nio.file.{Files, Paths}
import java.util.Date
import scala.collection.JavaConverters._
import scala.util.Random

class VertxRequestBody[F[_], S <: Streams[S]](
    serverOptions: VertxServerOptions[F],
    fromVFuture: FromVFuture[F]
)(implicit val readStreamCompatible: ReadStreamCompatible[S])
    extends RequestBody[F, S] {
  override val streams: Streams[S] = readStreamCompatible.streams

  // We can ignore maxBytes here, because vertx native body limit check is attached to endpoints by methods in sttp.tapir.server.vertx.handlers
  override def toRaw[R](serverRequest: ServerRequest, bodyType: RawBodyType[R], maxBytes: Option[Long]): F[RawValue[R]] = {
    val rc = routingContext(serverRequest)
    fromVFuture(bodyType match {
      case RawBodyType.StringBody(defaultCharset) =>
        Future.succeededFuture(RawValue(Option(rc.body().asString(defaultCharset.toString)).getOrElse("")))
      case RawBodyType.ByteArrayBody =>
        Future.succeededFuture(RawValue(Option(rc.body().buffer()).fold(Array.emptyByteArray)(_.getBytes)))
      case RawBodyType.ByteBufferBody =>
        Future.succeededFuture(RawValue(Option(rc.body().buffer()).fold(ByteBuffer.allocate(0))(_.getByteBuf.nioBuffer())))
      case RawBodyType.InputStreamBody =>
        val bytes = Option(rc.body().buffer()).fold(Array.emptyByteArray)(_.getBytes)
        Future.succeededFuture(RawValue(new ByteArrayInputStream(bytes)))
      case RawBodyType.InputStreamRangeBody =>
        val bytes = Option(rc.body().buffer()).fold(Array.emptyByteArray)(_.getBytes)
        Future.succeededFuture(RawValue(InputStreamRange(() => new ByteArrayInputStream(bytes))))
      case RawBodyType.FileBody =>
        rc.fileUploads().asScala.headOption match {
          case Some(upload) =>
            Future.succeededFuture {
              val file = FileRange(new File(upload.uploadedFileName()))
              RawValue(file, Seq(file))
            }
          case None if rc.body().buffer() != null =>
            val filePath = s"${serverOptions.uploadDirectory.getAbsolutePath}/tapir-${new Date().getTime}-${Random.nextLong()}"
            val fs = rc.vertx.fileSystem
            val result = fs
              .createFile(filePath)
              .flatMap(_ => fs.writeFile(filePath, rc.body().buffer()))
              .flatMap(_ =>
                Future.succeededFuture {
                  val file = FileRange(new File(filePath))
                  RawValue(file, Seq(file))
                }
              )
            result
          case None =>
            Future.failedFuture[RawValue[FileRange]]("No body")
        }
      case mp: RawBodyType.MultipartBody =>
        Future.succeededFuture {
          val formParts = rc
            .request()
            .formAttributes()
            .entries()
            .asScala
            .map { e =>
              mp.partType(e.getKey)
                .flatMap(rawBodyType => extractStringPart(e.getValue, rawBodyType))
                .map(body => Part(e.getKey, body))
            }
            .toList
            .flatten

          val fileParts = rc
            .fileUploads()
            .asScala
            .map { fu =>
              mp.partType(fu.name())
                .flatMap(rawBodyType => extractFilePart(fu, rawBodyType))
                .map(body =>
                  Part(fu.name(), body, contentType = MediaType.parse(fu.contentType()).toOption, fileName = Option(fu.fileName()))
                )
            }
            .toList
            .flatten

          RawValue.fromParts(formParts ++ fileParts)
        }
    })
  }

  override def toStream(serverRequest: ServerRequest, maxBytes: Option[Long]): streams.BinaryStream =
    readStreamCompatible
      .fromReadStream(routingContext(serverRequest).request, maxBytes)
      .asInstanceOf[streams.BinaryStream]

  private def extractStringPart[B](part: String, bodyType: RawBodyType[B]): Option[Any] = {
    bodyType match {
      case RawBodyType.StringBody(charset)  => Some(new String(part.getBytes(Charset.defaultCharset()), charset))
      case RawBodyType.ByteArrayBody        => Some(part.getBytes(Charset.defaultCharset()))
      case RawBodyType.ByteBufferBody       => Some(ByteBuffer.wrap(part.getBytes(Charset.defaultCharset())))
      case RawBodyType.InputStreamBody      => throw new IllegalArgumentException("Cannot create a multipart as an InputStream")
      case RawBodyType.InputStreamRangeBody => throw new IllegalArgumentException("Cannot create a multipart as an InputStream")
      case RawBodyType.FileBody             => None
      case RawBodyType.MultipartBody(_, _)  => None
    }
  }

  private def extractFilePart[B](fu: FileUpload, bodyType: RawBodyType[B]): Option[Any] = {
    bodyType match {
      case RawBodyType.StringBody(charset)  => Some(new String(readFileBytes(fu), charset))
      case RawBodyType.ByteArrayBody        => Some(readFileBytes(fu))
      case RawBodyType.ByteBufferBody       => Some(ByteBuffer.wrap(readFileBytes(fu)))
      case RawBodyType.InputStreamBody      => throw new IllegalArgumentException("Cannot create a multipart as an InputStream")
      case RawBodyType.InputStreamRangeBody => throw new IllegalArgumentException("Cannot create a multipart as an InputStream")
      case RawBodyType.FileBody             => Some(FileRange(new File(fu.uploadedFileName())))
      case RawBodyType.MultipartBody(_, _)  => None
    }
  }

  private def readFileBytes(fu: FileUpload) = Files.readAllBytes(Paths.get(fu.uploadedFileName()))

  private def routingContext(serverRequest: ServerRequest) = serverRequest.underlying.asInstanceOf[RoutingContext]
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy