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

io.finch.endpoint.body.scala Maven / Gradle / Ivy

package io.finch.endpoint

import cats.effect.Sync
import com.twitter.io.{Buf, Reader}
import io.finch._
import io.finch.internal._

import java.nio.charset.{Charset, StandardCharsets}
import scala.reflect.ClassTag

abstract private[finch] class FullBody[F[_], A] extends Endpoint[F, A] {

  protected def F: Sync[F]
  protected def missing: F[Output[A]]
  protected def present(contentType: String, content: Buf, cs: Charset): F[Output[A]]

  final def apply(input: Input): EndpointResult[F, A] =
    if (input.request.isChunked) EndpointResult.NotMatched[F]
    else {
      val output = F.defer {
        val contentLength = input.request.contentLengthOrNull
        if (contentLength == null || contentLength == "0") missing
        else
          present(
            input.request.mediaTypeOrEmpty,
            input.request.content,
            input.request.charsetOrUtf8
          )
      }

      EndpointResult.Matched(input, Trace.empty, output)
    }
}

private[finch] object FullBody {

  trait PreparedBody[F[_], A, B] { _: FullBody[F, B] =>
    protected def prepare(a: A): B
  }

  trait Required[F[_], A] extends PreparedBody[F, A, A] { _: FullBody[F, A] =>
    protected def prepare(a: A): A = a
    protected def missing: F[Output[A]] = F.raiseError(Error.BodyNotPresent)
  }

  trait Optional[F[_], A] extends PreparedBody[F, A, Option[A]] { _: FullBody[F, Option[A]] =>
    protected def prepare(a: A): Option[A] = Some(a)
    protected def missing: F[Output[Option[A]]] = F.pure(Output.None)
  }
}

abstract private[finch] class Body[F[_], A, B, CT](implicit
    dd: Decode.Dispatchable[A, CT],
    ct: ClassTag[A],
    protected val F: Sync[F]
) extends FullBody[F, B]
    with FullBody.PreparedBody[F, A, B] {

  protected def present(contentType: String, content: Buf, cs: Charset): F[Output[B]] =
    dd(contentType, content, cs) match {
      case Right(s) => F.pure(Output.payload(prepare(s)))
      case Left(e)  => F.raiseError(Error.BodyNotParsed(ct).initCause(e))
    }

  final override def toString: String = "body"
}

abstract private[finch] class BinaryBody[F[_], A](implicit protected val F: Sync[F]) extends FullBody[F, A] with FullBody.PreparedBody[F, Array[Byte], A] {

  protected def present(contentType: String, content: Buf, cs: Charset): F[Output[A]] =
    F.pure(Output.payload(prepare(content.asByteArray)))

  final override def toString: String = "binaryBody"
}

abstract private[finch] class StringBody[F[_], A](implicit protected val F: Sync[F]) extends FullBody[F, A] with FullBody.PreparedBody[F, String, A] {

  protected def present(contentType: String, content: Buf, cs: Charset): F[Output[A]] =
    F.pure(Output.payload(prepare(content.asString(cs))))

  final override def toString: String = "stringBody"
}

abstract private[finch] class ChunkedBody[F[_], S[_[_], _], A] extends Endpoint[F, S[F, A]] {

  protected def F: Sync[F]
  protected def prepare(r: Reader[Buf], cs: Charset): Output[S[F, A]]

  final def apply(input: Input): EndpointResult[F, S[F, A]] =
    if (!input.request.isChunked) EndpointResult.NotMatched[F]
    else
      EndpointResult.Matched(
        input,
        Trace.empty,
        F.delay(prepare(input.request.reader, input.request.charsetOrUtf8))
      )
}

final private[finch] class BinaryBodyStream[F[_], S[_[_], _]](implicit
    LR: LiftReader[S, F],
    protected val F: Sync[F]
) extends ChunkedBody[F, S, Array[Byte]]
    with (Buf => Array[Byte]) {

  def apply(buf: Buf): Array[Byte] = buf.asByteArray

  protected def prepare(r: Reader[Buf], cs: Charset): Output[S[F, Array[Byte]]] =
    Output.payload(LR(r, this))

  override def toString: String = "binaryBodyStream"
}

final private[finch] class StringBodyStream[F[_], S[_[_], _]](implicit
    LR: LiftReader[S, F],
    protected val F: Sync[F]
) extends ChunkedBody[F, S, String]
    with (Buf => String) {

  def apply(buf: Buf): String = buf.asString(StandardCharsets.UTF_8)

  protected def prepare(r: Reader[Buf], cs: Charset): Output[S[F, String]] = cs match {
    case StandardCharsets.UTF_8 => Output.payload(LR(r, this))
    case _                      => Output.payload(LR(r, _.asString(cs)))
  }

  override def toString: String = "stringBodyStream"
}

final private[finch] class BodyStream[F[_], S[_[_], _], A, CT <: String](implicit
    protected val F: Sync[F],
    LR: LiftReader[S, F],
    A: DecodeStream.Aux[S, F, A, CT]
) extends ChunkedBody[F, S, A] {

  protected def prepare(r: Reader[Buf], cs: Charset): Output[S[F, A]] =
    Output.payload(A(LR(r), cs))

  override def toString: String = "bodyStream"
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy