Skip to content

Instantly share code, notes, and snippets.

@negator
Last active December 10, 2015 01:48
Show Gist options
  • Save negator/4361424 to your computer and use it in GitHub Desktop.
Save negator/4361424 to your computer and use it in GitHub Desktop.
General purpose Url class for silky smooth url string manipulation and sugary Play! WS integration. Enjoy responsibly.
import play.api.libs.ws._
import play.api.libs.json._
import scalaz._
import Scalaz._
import java.net.{ URL => JavaUrl }
/* This class makes url manipulation a piece of cake (dark chocolate). It also includes Play WS request generation.
Requires scalaz6 and Play 2.0.4
Examples:
//Facebook url: "https://graph.facebook.com/12344/likes?token=abcd"
val facebookUrl = https("graph.facebook.com") / "1234" / "likes" +? ("token"->"abcd")
//Add 'limit' query param
val newUrl = facebookUrl +? ("limit"->10)
//Remove token param
val newUrl = facebookUrl -? ("token")
//Create WSRequest and invoke get
facebookUrl.toWsRequest.get
*/
sealed abstract class Protocol(val name: String)
object Http extends Protocol("http")
object Https extends Protocol("https")
case class Url(
protocol: Protocol,
host: String,
port: Option[Int] = None,
paths: Seq[String] = Nil,
params: Seq[(String, String)] = Nil) {
/*Add path param*/
def /(path: String): Url = copy(paths = paths ++ Seq(path))
/*Add query param*/
def +?(keyValue: (String, Any)): Url = copy(params = params ++ Seq(keyValue._1 -> keyValue._2.toString))
/*Remove query param*/
def -?(key: String): Url = copy(params = params.filterNot(_._1 == key))
/*Update queryparam*/
def ^?(keyValue: (String, Any)): Url = -?(keyValue._1) +? (keyValue)
/*Value of query param*/
def ??(key: String): Option[String] = params.find(_._1 == key).map(_._2)
def %(args: String*) = format(args: _*)
def format(args: String*) = Url(toString format (args: _*))
override lazy val toString = throughPaths + queryString
lazy val toJavaURL = new java.net.URL(toString)
lazy val toWsRequest = WS.url(throughPaths).withQueryString(params: _*)
private lazy val throughPaths: String = {
// Start with protocol.
val builder = new StringBuilder(protocol.name)
// Add host and port.
builder ++= "://" ++= host ++= (port.map(_.toString) | "")
// Add paths.
paths foreach { builder ++= "/" ++= _ }
// Get result.
builder.toString
}
private lazy val queryString: String = {
// Start with empty String.
val builder = new StringBuilder("")
// The sep is first "?" and becomes "&" after.
var sep = "?"
// Add query params starting with sep.
params foreach {
case (key, value) =>
builder ++= sep ++= key ++= "=" ++= value
sep = "&"
}
// Get result.
builder.toString
}
}
object Url {
def apply(url: String): Url = {
val javaurl = new JavaUrl(url)
val protocol = javaurl.getProtocol match {
case "https" => Https
case _ => Http
}
val host = javaurl.getHost
val port = javaurl.getPort match {
case -1 => None //Java URL returns -1 when port not specified.
case p => Some(p)
}
val paths = javaurl.getPath.stripPrefix("/").split("/").toList
val params = Option(javaurl.getQuery) map {
_.split("&").toList map (_.split("=", 2)) collect {
case Array(key, value) => key -> value
}
}
Url(protocol, host, port, paths, params | Nil)
}
def http(host: String) = Url(Http, host)
def https(host: String) = Url(Https, host)
implicit val urlFormat: Format[Url] = new Format[Url] {
def writes(url: Url) = Json toJson url.toString
def reads(js: JsValue) = Url(Json.fromJson[String](js))
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment