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

com.netaporter.uri.Uri.scala Maven / Gradle / Ivy

The newest version!
package com.netaporter.uri

import com.netaporter.uri.inet.PublicSuffixes
import com.netaporter.uri.parsing.UriParser
import com.netaporter.uri.config.UriConfig
import com.netaporter.uri.Parameters.Param
import scala.collection.GenTraversableOnce
import scala.collection.Seq

/**
 * http://tools.ietf.org/html/rfc3986
 */
case class Uri (
  scheme: Option[String],
  user: Option[String],
  password: Option[String],
  host: Option[String],
  port: Option[Int],
  pathParts: Seq[PathPart],
  query: QueryString,
  fragment: Option[String]
) {

  lazy val hostParts: Seq[String] =
    host.map(h => h.split('.').toVector).getOrElse(Vector.empty)

  def subdomain = hostParts.headOption

  def pathPartOption(name: String) =
    pathParts.find(_.part == name)

  def pathPart(name: String) =
    pathPartOption(name).head

  def matrixParams =
    pathParts.last match {
      case MatrixParams(_, p) => p
      case _ => Seq.empty
    }

  def addMatrixParam(pp: String, k: String, v: String) = copy (
    pathParts = pathParts.map {
      case p: PathPart if p.part == pp => p.addParam(k -> Some(v))
      case x => x
    }
  )

  def addMatrixParam(k: String, v: String) = copy (
    pathParts = pathParts.dropRight(1) :+ pathParts.last.addParam(k -> Some(v))
  )

  /**
   * Adds a new Query String parameter key-value pair. If the value for the Query String parmeter is None, then this
   * Query String parameter will not be rendered in calls to toString or toStringRaw
   * @param name name of the parameter
   * @param value value for the parameter
   * @return A new Uri with the new Query String parameter
   */
  def addParam(name: String, value: Any) = (name, value) match {
    case (_, None) => this
    case (n, Some(v)) => copy(query = query.addParam(n, Some(v.toString)))
    case (n, v) => copy(query = query.addParam(n, Some(v.toString)))
  }

  def addParams(kvs: Seq[(String, Any)]) = {
    val cleanKvs = kvs.filterNot(_._2 == None).map {
      case (k, Some(v)) => (k, Some(v.toString))
      case (k, v) => (k, Some(v.toString))
    }
    copy(query = query.addParams(cleanKvs))
  }

  def protocol = scheme

  /**
   * Copies this Uri but with the scheme set as the given value.
   *
   * @param scheme the new scheme to set
   * @return a new Uri with the specified scheme
   */
  def withScheme(scheme: String): Uri = copy(scheme = Option(scheme))

  /**
   * Copies this Uri but with the host set as the given value.
   *
   * @param host the new host to set
   * @return a new Uri with the specified host
   */
  def withHost(host: String): Uri = copy(host = Option(host))

  /**
   * Copies this Uri but with the user set as the given value.
   *
   * @param user the new user to set
   * @return a new Uri with the specified user
   */
  def withUser(user: String): Uri = copy(user = Option(user))

  /**
   * Copies this Uri but with the password set as the given value.
   *
   * @param password the new password to set
   * @return a new Uri with the specified password
   */
  def withPassword(password: String): Uri = copy(password = Option(password))

  /**
   * Copies this Uri but with the port set as the given value.
   *
   * @param port the new port to set
   * @return a new Uri with the specified port
   */
  def withPort(port: Int): Uri = copy(port = Option(port))

  /**
   * Copies this Uri but with the fragment set as the given value.
   *
   * @param fragment the new fragment to set
   * @return a new Uri with the specified fragment
   */
  def withFragment(fragment: String): Uri = copy(fragment = Option(fragment))

  /**
   * Returns the path with no encoders taking place (e.g. non ASCII characters will not be percent encoded)
   * @return String containing the raw path for this Uri
   */
  def pathRaw(implicit c: UriConfig = UriConfig.default) =
    path(c.withNoEncoding)

  /**
   * Returns the encoded path. By default non ASCII characters in the path are percent encoded.
   * @return String containing the path for this Uri
   */
  def path(implicit c: UriConfig = UriConfig.default) =
    if(pathParts.isEmpty) ""
    else "/" + pathParts.map(_.partToString(c)).mkString("/")

  def queryStringRaw(implicit c: UriConfig = UriConfig.default) =
    queryString(c.withNoEncoding)

  def queryString(implicit c: UriConfig = UriConfig.default) =
    query.queryToString(c)

  /**
   * Replaces the all existing Query String parameters with the specified key with a single Query String parameter
   * with the specified value. If the value passed in is None, then all Query String parameters with the specified key
   * are removed
   *
   * @param k Key for the Query String parameter(s) to replace
   * @param v value to replace with
   * @return A new Uri with the result of the replace
   */
  def replaceParams(k: String, v: Any) = {
    v match {
      case valueOpt: Option[_] =>
        copy(query = query.replaceAll(k, valueOpt))
      case _ =>
        copy(query = query.replaceAll(k, Some(v)))
    }
  }

  /**
   * Replaces the all existing Query String parameters with a new set of query params
   */
  def replaceAllParams(params: Param*) =
    copy(query = query.withParams(params))

  /**
   * Transforms the Query String by applying the specified Function to each Query String Parameter
   *
   * @param f A function that returns a new Parameter when applied to each Parameter
   * @return
   */
  def mapQuery(f: Param=>Param) =
    copy(query = query.mapParams(f))

  /**
   * Transforms the Query String by applying the specified Function to each Query String Parameter
   *
   * @param f A function that returns a collection of Parameters when applied to each parameter
   * @return
   */
  def flatMapQuery(f: Param=>GenTraversableOnce[Param]) =
    copy(query = query.flatMapParams(f))

  /**
   * Transforms the Query String by applying the specified Function to each Query String Parameter name
   *
   * @param f A function that returns a new Parameter name when applied to each Parameter name
   * @return
   */
  def mapQueryNames(f: String=>String) =
    copy(query = query.mapParamNames(f))

  /**
   * Transforms the Query String by applying the specified Function to each Query String Parameter value
   *
   * @param f A function that returns a new Parameter value when applied to each Parameter value
   * @return
   */
  def mapQueryValues(f: String=>String) =
    copy(query = query.mapParamValues(f))

  /**
   * Removes any Query String Parameters that return false when applied to the given Function
   *
   * @param f
   * @return
   */
  def filterQuery(f: Param=>Boolean) =
    copy(query = query.filterParams(f))

  /**
   * Removes any Query String Parameters that return false when their name is applied to the given Function
   *
   * @param f
   * @return
   */
  def filterQueryNames(f: String=>Boolean) =
    copy(query = query.filterParamsNames(f))

  /**
   * Removes any Query String Parameters that return false when their value is applied to the given Function
   *
   * @param f
   * @return
   */
  def filterQueryValues(f: String=>Boolean) =
    copy(query = query.filterParamsValues(f))

  /**
   * Removes all Query String parameters with the specified key
   * @param k Key for the Query String parameter(s) to remove
   * @return
   */
  def removeParams(k: String) = {
    copy(query = query.removeAll(k))
  }

  /**
   * Removes all Query String parameters with the specified key contained in the a (Array)
   * @param a an Array of Keys for the Query String parameter(s) to remove
   * @return
   */
  def removeParams(a: Seq[String]) = {
    copy(query = query.removeAll(a))
  }

  /**
   * Removes all Query String parameters
   * @return
   */
  def removeAllParams() = {
    copy(query = query.removeAll())
  }

  def publicSuffix: Option[String] = {
    for {
      h <- host
      longestMatch <- PublicSuffixes.trie.longestMatch(h.reverse)
    } yield longestMatch.reverse
  }

  def publicSuffixes: Seq[String] = {
    for {
      h <- host.toSeq
      m <- PublicSuffixes.trie.matches(h.reverse)
    } yield m.reverse
  }

  override def toString = toString(UriConfig.default)

  def toString(implicit c: UriConfig = UriConfig.default): String = {
    //If there is no scheme, we use scheme relative
    def userInfo = for {
      userStr <- user
      userStrEncoded = c.userInfoEncoder.encode(userStr, c.charset)
      passwordStrEncoded = password.map(p => ":" + c.userInfoEncoder.encode(p, c.charset)).getOrElse("")
    } yield userStrEncoded + passwordStrEncoded + "@"

    val hostStr = for {
      hostStr <- host
      schemeStr = scheme.map(_ + "://").getOrElse("//")
      userInfoStr = userInfo.getOrElse("")
    } yield schemeStr + userInfoStr + hostStr

    hostStr.getOrElse("") +
      port.map(":" + _).getOrElse("") +
      path(c) +
      queryString(c) +
      fragment.map(f => "#" + c.fragmentEncoder.encode(f, c.charset)).getOrElse("")
  }

  /**
   * Returns the string representation of this Uri with no encoders taking place
   * (e.g. non ASCII characters will not be percent encoded)
   * @return String containing this Uri in it's raw form
   */
  def toStringRaw(implicit config: UriConfig = UriConfig.default): String =
    toString(config.withNoEncoding)

  /**
   * Converts to a Java URI.
   * This involves a toString + URI.parse because the specific URI constructors do not deal properly with encoded elements
   * @return a URI matching this Uri
   */
  def toURI(implicit c: UriConfig = UriConfig.conservative) = new java.net.URI(toString())
}

object Uri {

  def parse(s: CharSequence)(implicit config: UriConfig = UriConfig.default): Uri =
    UriParser.parse(s.toString, config)


  def parseQuery(s: CharSequence)(implicit config: UriConfig = UriConfig.default): QueryString =
    UriParser.parseQuery(s.toString, config)

  def apply(scheme: String = null,
            user: String = null,
            password: String = null,
            host: String = null,
            port: Int = 0,
            pathParts: Seq[PathPart] = Seq.empty,
            query: QueryString = EmptyQueryString,
            fragment: String = null) = {
      new Uri(Option(scheme),
        Option(user),
        Option(password),
        Option(host),
        if(port > 0) Some(port) else None,
        pathParts,
        query,
        Option(fragment)
      )
  }

  def empty = apply()

  def apply(javaUri: java.net.URI): Uri = parse(javaUri.toASCIIString())
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy