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

org.http4s.EntityDecoder.scala Maven / Gradle / Ivy

There is a newer version: 0.16.6a
Show newest version
package org.http4s

import java.io.{File, FileOutputStream, StringReader}
import javax.xml.parsers.SAXParser

import org.xml.sax.{InputSource, SAXParseException}
import java.io.{File, FileOutputStream}
import org.http4s.headers.`Content-Type`
import org.http4s.multipart.{Multipart, MultipartDecoder}
import scodec.bits.ByteVector

import scala.annotation.unchecked.uncheckedVariance
import scala.util.control.NonFatal
import scalaz.Liskov.{<~<, refl}
import scalaz.concurrent.Task
import scalaz.std.string._
import scalaz.stream.{io, process1}
import scalaz.syntax.monad._
import scalaz.{-\/, EitherT, \/, \/-}

import util.UrlFormCodec.{ decode => formDecode }
import util.byteVector._

/** A type that can be used to decode a [[Message]]
  * EntityDecoder is used to attempt to decode a [[Message]] returning the
  * entire resulting A. If an error occurs it will result in a failed Task
  * The default decoders provided here are not streaming, but one could implement
  * a streaming decoder by having the value of A be some kind of streaming construct.
  * @tparam T result type produced by the decoder
  */
trait EntityDecoder[T] { self =>
  /** Attempt to decode the body of the [[Message]] */
  def decode(msg: Message, strict: Boolean): DecodeResult[T]

  /** The [[MediaRange]]s this [[EntityDecoder]] knows how to handle */
  def consumes: Set[MediaRange]

  /** Make a new [[EntityDecoder]] by mapping the output result */
  def map[T2](f: T => T2): EntityDecoder[T2] = new EntityDecoder[T2] {
    override def consumes: Set[MediaRange] = self.consumes

    override def decode(msg: Message, strict: Boolean): DecodeResult[T2] = self.decode(msg, strict).map(f)
  }

  def flatMapR[T2](f: T => DecodeResult[T2]): EntityDecoder[T2] = new EntityDecoder[T2] {
    override def decode(msg: Message, strict: Boolean): DecodeResult[T2] = self.decode(msg, strict).flatMap(f)

    override def consumes: Set[MediaRange] = self.consumes
  }

  /** Combine two [[EntityDecoder]]'s
    *
    * The new [[EntityDecoder]] will first attempt to determine if it can perform the decode,
    * and if not, defer to the second [[EntityDecoder]]
    * @param other backup [[EntityDecoder]]
    */
  def orElse[T2](other: EntityDecoder[T2])(implicit ev: T <~< T2): EntityDecoder[T2] =
    new EntityDecoder.OrDec(widen[T2], other)

  /** true if this [[EntityDecoder]] knows how to decode the provided [[MediaType]] */
  def matchesMediaType(mediaType: MediaType): Boolean =
    consumes.exists(_.satisfiedBy(mediaType))

  // shamelessly stolen from IList
  def widen[B](implicit ev: T <~< B): EntityDecoder[B] =
    ev.subst[({type λ[-α] = EntityDecoder[α @uncheckedVariance] <~< EntityDecoder[B]})#λ](refl)(this)
}

/** EntityDecoder is used to attempt to decode an [[EntityBody]]
  * This companion object provides a way to create `new EntityDecoder`s along
  * with some commonly used instances which can be resolved implicitly.
  */
object EntityDecoder extends EntityDecoderInstances {

  // This is not a real media type but will still be matched by `*/*`
  private val UndefinedMediaType = new MediaType("UNKNOWN","UNKNOWN")

  /** summon an implicit [[EntityEncoder]] */
  def apply[T](implicit ev: EntityDecoder[T]): EntityDecoder[T] = ev

  /** Create a new [[EntityDecoder]]
    *
    * The new [[EntityDecoder]] will attempt to decode messages of type `T`
    * only if the [[Message]] satisfies the provided [[MediaRange]]s
    */
  def decodeBy[T](r1: MediaRange, rs: MediaRange*)(f: Message => DecodeResult[T]): EntityDecoder[T] = new EntityDecoder[T] {
    override def decode(msg: Message, strict: Boolean): DecodeResult[T] = {
      try {
        if (strict) msg.headers.get(`Content-Type`) match {
          case Some(c) if matchesMediaType(c.mediaType) => f(msg)
          case Some(c) => DecodeResult.failure(MediaTypeMismatch(c.mediaType, consumes))
          case None if matchesMediaType(UndefinedMediaType) => f(msg)
          case None => DecodeResult.failure(MediaTypeMissing(consumes))
        }
        else f(msg)
      }
      catch {
        case NonFatal(e) => DecodeResult.failure(MalformedMessageBodyFailure("Error decoding body", Some(e)))
      }
    }

    override val consumes: Set[MediaRange] = (r1 +: rs).toSet
  }

  private class OrDec[T](a: EntityDecoder[T], b: EntityDecoder[T]) extends EntityDecoder[T] {
    override def decode(msg: Message, strict: Boolean): DecodeResult[T] = {
      msg.headers.get(`Content-Type`) match {
        case Some(contentType) =>
          if (a.matchesMediaType(contentType.mediaType)) a.decode(msg, strict)
          else b.decode(msg, strict).leftMap{
            case MediaTypeMismatch(actual, expected) =>
              MediaTypeMismatch(actual, expected ++ a.consumes)
            case other => other
          }

        case None =>
          if (a.matchesMediaType(UndefinedMediaType)) a.decode(msg, strict)
          else b.decode(msg, strict).leftMap{
            case MediaTypeMissing(expected) =>
              MediaTypeMissing(expected ++ a.consumes)
            case other => other
          }
      }
    }

    override val consumes: Set[MediaRange] = a.consumes ++ b.consumes
  }

  /** Helper method which simply gathers the body into a single ByteVector */
  def collectBinary(msg: Message): DecodeResult[ByteVector] =
    DecodeResult.success(msg.body.runFoldMap(identity))

  /** Decodes a message to a String */
  def decodeString(msg: Message)(implicit defaultCharset: Charset = DefaultCharset): Task[String] =
    msg.bodyAsText.foldMonoid.runLastOr("")
}

/** Implementations of the EntityDecoder instances */
trait EntityDecoderInstances {
  import org.http4s.EntityDecoder._

  /////////////////// Instances //////////////////////////////////////////////

  /** Provides a mechanism to fail decoding */
  def error[T](t: Throwable) = new EntityDecoder[T] {
    override def decode(msg: Message, strict: Boolean): DecodeResult[T] = {
      DecodeResult(msg.body.kill.run.flatMap(_ => Task.fail(t)))
    }
    override def consumes: Set[MediaRange] = Set.empty
  }

  implicit val binary: EntityDecoder[ByteVector] = {
    EntityDecoder.decodeBy(MediaRange.`*/*`)(collectBinary)
  }

  implicit def text(implicit defaultCharset: Charset = DefaultCharset): EntityDecoder[String] =
    EntityDecoder.decodeBy(MediaRange.`text/*`)(msg =>
      collectBinary(msg).map(bs => new String(bs.toArray, msg.charset.getOrElse(defaultCharset).nioCharset))
    )

  // File operations // TODO: rewrite these using NIO non blocking FileChannels, and do these make sense as a 'decoder'?
  def binFile(file: File): EntityDecoder[File] =
    EntityDecoder.decodeBy(MediaRange.`*/*`){ msg =>
      val p = io.chunkW(new java.io.FileOutputStream(file))
      DecodeResult.success(msg.body.to(p).run).map(_ => file)
    }

  def textFile(file: java.io.File): EntityDecoder[File] =
    EntityDecoder.decodeBy(MediaRange.`text/*`){ msg =>
      val p = io.chunkW(new java.io.PrintStream(new FileOutputStream(file)))
      DecodeResult.success(msg.body.to(p).run).map(_ => file)
    }

  implicit def multipart: EntityDecoder[Multipart] = 
    MultipartDecoder.decoder
}

object DecodeResult {
  def apply[A](task: Task[DecodeFailure \/ A]): DecodeResult[A] = EitherT(task)

  def success[A](a: Task[A]): DecodeResult[A] = EitherT.right(a)

  def success[A](a: A): DecodeResult[A] = EitherT(Task.now(\/.right[DecodeFailure, A](a)))

  def failure[A](e: Task[DecodeFailure]): DecodeResult[A] = EitherT.left(e)

  def failure[A](e: DecodeFailure): DecodeResult[A] = EitherT(Task.now(-\/(e): DecodeFailure\/A))
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy