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

sttp.tapir.server.ziohttp.ZioHttpToResponseBody.scala Maven / Gradle / Ivy

There is a newer version: 1.11.10
Show newest version
package sttp.tapir.server.ziohttp

import sttp.capabilities.zio.ZioStreams
import sttp.model.HasHeaders
import sttp.model.Part
import sttp.tapir.server.interpreter.ToResponseBody
import sttp.tapir.{CodecFormat, RawBodyType, RawPart, WebSocketBodyOutput}
import zio.Chunk
import zio.http.FormField
import zio.http.MediaType
import zio.stream.ZStream

import java.io.InputStream
import java.nio.ByteBuffer
import java.nio.charset.Charset

class ZioHttpToResponseBody extends ToResponseBody[ZioResponseBody, ZioStreams] {
  override val streams: ZioStreams = ZioStreams

  override def fromRawValue[R](
      v: R,
      headers: HasHeaders,
      format: CodecFormat,
      bodyType: RawBodyType[R]
  ): ZioResponseBody =
    Right(rawValueToEntity(bodyType, v))

  override def fromStreamValue(
      v: streams.BinaryStream,
      headers: HasHeaders,
      format: CodecFormat,
      charset: Option[Charset]
  ): ZioResponseBody = Right(ZioStreamHttpResponseBody(v, None))

  override def fromWebSocketPipe[REQ, RESP](
      pipe: streams.Pipe[REQ, RESP],
      o: WebSocketBodyOutput[streams.Pipe[REQ, RESP], REQ, RESP, _, ZioStreams]
  ): ZioResponseBody =
    Left(ZioWebSockets.pipeToBody(pipe, o))

  private def rawValueToEntity[R](bodyType: RawBodyType[R], r: R): ZioHttpResponseBody =
    bodyType match {
      case RawBodyType.StringBody(charset) =>
        val bytes = r.toString.getBytes(charset)
        ZioRawHttpResponseBody(Chunk.fromArray(bytes), Some(bytes.length.toLong))
      case RawBodyType.ByteArrayBody =>
        ZioRawHttpResponseBody(Chunk.fromArray(r), Some((r: Array[Byte]).length.toLong))
      case RawBodyType.ByteBufferBody =>
        val buffer: ByteBuffer = r
        ZioRawHttpResponseBody(Chunk.fromByteBuffer(buffer), Some(buffer.remaining()))
      case RawBodyType.InputStreamBody =>
        ZioStreamHttpResponseBody(ZStream.fromInputStream(r), None)
      case RawBodyType.InputStreamRangeBody =>
        r.range
          .map(range =>
            ZioStreamHttpResponseBody(
              ZStream.fromInputStream(r.inputStreamFromRangeStart()).take(range.contentLength),
              Some(range.contentLength)
            )
          )
          .getOrElse(ZioStreamHttpResponseBody(ZStream.fromInputStream(r.inputStream()), None))
      case RawBodyType.FileBody =>
        val tapirFile = r
        tapirFile.range
          .flatMap { r =>
            r.startAndEnd.map { s =>
              var count = 0L
              ZioStreamHttpResponseBody(
                ZStream
                  .fromPath(tapirFile.file.toPath)
                  .dropWhile(_ =>
                    if (count < s._1) { count += 1; true }
                    else false
                  )
                  .take(r.contentLength),
                Some(r.contentLength)
              )
            }
          }
          .getOrElse(ZioStreamHttpResponseBody(ZStream.fromPath(tapirFile.file.toPath), Some(tapirFile.file.length)))
      case m @ RawBodyType.MultipartBody(_, _) =>
        val formFields = (r: Seq[RawPart]).flatMap { part =>
          m.partType(part.name).map { partType =>
            toFormField(partType.asInstanceOf[RawBodyType[Any]], part)
          }
        }.toList
        ZioMultipartHttpResponseBody(formFields)
    }

  private def toFormField[R](bodyType: RawBodyType[R], part: Part[R]): FormField = {
    val mediaType: Option[MediaType] = part.contentType.flatMap(MediaType.forContentType)
    bodyType match {
      case RawBodyType.StringBody(_) =>
        FormField.Text(part.name, part.body, mediaType.getOrElse(MediaType.text.plain), part.fileName)
      case RawBodyType.ByteArrayBody =>
        FormField.Binary(
          part.name,
          Chunk.fromArray(part.body),
          mediaType.getOrElse(MediaType.application.`octet-stream`),
          filename = part.fileName
        )
      case RawBodyType.ByteBufferBody =>
        val array: Array[Byte] = new Array[Byte](part.body.remaining)
        part.body.get(array)
        FormField.Binary(
          part.name,
          Chunk.fromArray(array),
          mediaType.getOrElse(MediaType.application.`octet-stream`),
          filename = part.fileName
        )
      case RawBodyType.FileBody =>
        FormField.streamingBinaryField(
          part.name,
          ZStream.fromFile(part.body.file).orDie,
          mediaType.getOrElse(MediaType.application.`octet-stream`),
          filename = part.fileName
        )
      case RawBodyType.InputStreamBody =>
        FormField.streamingBinaryField(
          part.name,
          ZStream.fromInputStream(part.body).orDie,
          mediaType.getOrElse(MediaType.application.`octet-stream`),
          filename = part.fileName
        )
      case RawBodyType.InputStreamRangeBody =>
        FormField.streamingBinaryField(
          part.name,
          ZStream.fromInputStream(part.body.inputStream()).orDie,
          mediaType.getOrElse(MediaType.application.`octet-stream`),
          filename = part.fileName
        )
      case _: RawBodyType.MultipartBody =>
        throw new UnsupportedOperationException("Nested multipart messages are not supported.")
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy