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

com.nrinaudo.fetch.QueryString.scala Maven / Gradle / Ivy

The newest version!
package com.nrinaudo.fetch

import scala.util.{Success, Failure, Try}


object QueryString {
  // - Implicit formats ------------------------------------------------------------------------------------------------
  // -------------------------------------------------------------------------------------------------------------------
  implicit val Doubles: ValueFormat[Double]   = ValueFormat.Doubles
  implicit val Longs: ValueFormat[Long]       = ValueFormat.Longs
  implicit val Shorts: ValueFormat[Short]     = ValueFormat.Shorts
  implicit val Ints: ValueFormat[Int]         = ValueFormat.Ints
  implicit val Bytes: ValueFormat[Byte]       = ValueFormat.Bytes
  implicit val Floats: ValueFormat[Float]     = ValueFormat.Floats
  implicit val Booleans: ValueFormat[Boolean] = ValueFormat.Booleans
  implicit val Strings: ValueFormat[String]   = ValueFormat.Strings
  implicit val Chars: ValueFormat[Char]       = ValueFormat.Chars



  // - Instance creation -----------------------------------------------------------------------------------------------
  // -------------------------------------------------------------------------------------------------------------------
  def apply(content: Map[String, List[String]] = Map()): QueryString = new QueryString(content)

  def apply(query: String): QueryString = {
    def extract(str: String) = new QueryString(str.split("&").foldLeft(Map[String, List[String]]()) {
      case (map, entry) =>
        val (n, v) = entry.splitAt(entry.indexOf('='))

        val name  = UrlEncoder.decode(n)
        val value = UrlEncoder.decode(v.substring(1))

        // TODO: appending at the end of the list has a complexity of O(n) and is inefficient for long lists. Fix.
        map + (name -> map.get(name).fold(List(value))(_ :+ value))
    })

    // Protection against java.net.URL.getQuery returning null rather than the empty string.
    if(query == null || query.isEmpty) new QueryString(Map())
    else if(query.charAt(0) == '?')    extract(query.substring(1))
    else                               extract(query)
  }
}

/** Represents an [[Url]]'s query string. */
class QueryString private (content: Map[String, List[String]]) {
  val values = content.mapValues(_.filter(!_.isEmpty)).filter {
    case (name, entries) => !(name.isEmpty || entries.isEmpty)
  }


  // - Parameter setting -----------------------------------------------------------------------------------------------
  // -------------------------------------------------------------------------------------------------------------------
  /** Returns `Some[List[String]]` if the specified values map to actual content, `None` otherwise.
    *
    * The purpose of this method is to filter out empty value lists.
    */
  private def writeValues[T: ValueWriter](values: T*) =
    ValueWriter.sequence(values).map(_.filter(!_.isEmpty)).filter(!_.isEmpty)

  /** Sets the specified parameter to the specified value.
    *
    * The main purpose of this method is to let application developers write code such as:
    * {{{
    * new QueryString() & ("param1" -> "value1") & ("param2" -> "value2")
    * }}}
    * Note that this method only accepts a single value, and will always replace any previous value for the specified
    * name. If you need to specify the same parameter multiple times, use [[add]] instead.
    *
    * @param  param tuple whose first entry if the parameter's name and second entry its value.
    * @tparam T     type of the value to set.
    * @return       an updated query string.
    */
  def &[T: ValueWriter](param: (String, T)): QueryString = set(param._1, param._2)

  def add[T: ValueWriter](name: String, values: T*): QueryString = writeValues(values: _*).fold(this) { list =>
    new QueryString(this.values + (name -> this.values.get(name).fold(list.toList) {_ ::: list.toList}))
  }

  def set[T: ValueWriter](name: String, values: T*): QueryString = writeValues(values: _*).fold(this) { list =>
    new QueryString(this.values + (name -> list.toList))
  }



  // - Parameter getting -----------------------------------------------------------------------------------------------
  // -------------------------------------------------------------------------------------------------------------------
  def getFirst[T: ValueReader](name: String): Option[Try[T]] =  get[T](name) map (_ map (_(0)))

  def get[T: ValueReader](name: String): Option[Try[List[T]]] = values.get(name).map { list =>
    ValueReader.sequence(list) match {
      case Some(l) => Success(l)
      case None    => Failure(new IllegalArgumentException("Illegal value: " + list))
    }
  }

  def getFirstOpt[T: ValueReader](name: String): Option[T] = getOpt[T](name) map(_(0))

  def getOpt[T: ValueReader](name: String): Option[List[T]] = for {
    v1 <- values.get(name)
    v2 <- ValueReader.sequence(v1)(implicitly[ValueReader[T]])
  } yield v2

  def apply[T: ValueReader](name: String): List[T] = values(name) map { v =>
    implicitly[ValueReader[T]].read(v).getOrElse(throw new IllegalArgumentException("Illegal value: " + v))
  }

  def first[T: ValueReader](name: String): T = apply[T](name).apply(0)



  // - Object implementation -------------------------------------------------------------------------------------------
  // -------------------------------------------------------------------------------------------------------------------
  override def hashCode(): Int = values.hashCode()

  override def equals(p1: scala.Any): Boolean = p1 match {
    case q: QueryString => q.values == values
    case _              => false
  }



  // - Serialization ---------------------------------------------------------------------------------------------------
  // -------------------------------------------------------------------------------------------------------------------
  def writeTo(builder: StringBuilder): StringBuilder = {
    var first = true

    for {
      (name, list) <- values
      value        <- list
    } {
      if(first) first = false
      else      builder.append("&")

      builder.append(UrlEncoder.encode(name)).append('=').append(UrlEncoder.encode(value))
    }

    builder
  }

  override lazy val toString: String = writeTo(new StringBuilder).result()
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy