Important
scala-uri is not currently being actively maintained in this repo
There is a maintained fork at https://github.com/indoorvivants/scala-uri
scala-uri is a small Scala library that helps you work with URIs. It has the following features:
- A RFC 3986 compliant parser to parse URLs and URNs from Strings
- URL Builders to create URLs from scratch
- Ability to transform query strings with methods such as filterQuery and mapQuery
- Ability to replace and remove query string parameters
- Ability to extract TLDs and public suffixes such as
.comand.co.ukfrom hosts - Ability to render URLs in punycode
- Ability to parse IPv6 and IPv4 addresses
- Support for custom encoding such as encoding spaces as pluses
- Support for protocol relative urls
- Support for user information e.g.
ftp://user:[email protected] - Support for URNs
- Support for mailto URLs
- Support for data URLs as defined in RFC2397
- Support for git scp-like URLs
- Support for Scala.js
- Support for cats
- No dependencies on existing web frameworks
To include it in your SBT project from maven central:
"io.lemonlabs" %% "scala-uri" % "4.0.3"There are also demo projects for both scala and Scala.js to help you get up and running quickly.
import io.lemonlabs.uri.Url
val url = Url.parse("https://www.scala-lang.org")The returned value has type Url with an underlying implementation of AbsoluteUrl, RelativeUrl,
UrlWithoutAuthority, ProtocolRelativeUrl or DataUrl. If you know your URL will always be one of these types, you can
use the following parse methods to get a more specific return type
import io.lemonlabs.uri._
val absoluteUrl = AbsoluteUrl.parse("https://www.scala-lang.org")
val relativeUrl = RelativeUrl.parse("/index.html")
val mailtoUrl = UrlWithoutAuthority.parse("mailto:[email protected]")
val protocolRelativeUrl = ProtocolRelativeUrl.parse("//www.scala-lang.org")
val dataUrl = DataUrl.parse("data:text/plain;base64,SGVsbG8sIFdvcmxkIQ%3D%3D")Note: scala-uri only supports parsing port numbers less than Int.MaxValue, deviating from RFC3986 which does
not impose a limit
import io.lemonlabs.uri.Urn
val urn = Urn.parse("urn:isbn:0981531687")
urn.scheme // This is "urn"
urn.nid // This is "isbn"
urn.nss // This is "0981531687"You can use Uri.parse to parse URNs as well as URLs. Url.parse and Urn.parse are preferable as they return
a more specific return type
Url provides an apply method with a bunch of optional parameters that can be used to build URLs
import io.lemonlabs.uri.{Url, QueryString}
val url = Url(scheme = "http", host = "lemonlabs.io", path = "/opensource")
val url2 = Url(path = "/opensource", query = QueryString.fromPairs("param1" -> "a", "param2" -> "b"))The mapQuery method will transform the Query String of a URI by applying the specified PartialFunction to each
Query String Parameter. Any parameters not matched in the PartialFunction will be left as-is.
import io.lemonlabs.uri.Url
val uri = Url.parse("/scala-uri?p1=one&p2=2&p3=true")
// Results in /scala-uri?p1_map=one_map&p2_map=2_map&p3_map=true_map
uri.mapQuery {
case (n, Some(v)) => (n + "_map", Some(v + "_map"))
}The mapQueryNames and mapQueryValues provide a more convenient way to transform just Query Parameter names or values
import io.lemonlabs.uri.Url
val uri = Url.parse("/scala-uri?p1=one&p2=2&p3=true")
uri.mapQueryNames(_.toUpperCase) // Results in /scala-uri?P1_map=one&P2=2&P3=true
uri.mapQueryValues(_.replace("true", "false")) // Results in /scala-uri?p1=one&p2=2&p3=falseThe filterQuery method will remove any Query String Parameters for which the provided Function returns false
import io.lemonlabs.uri.Url
val uri = Url.parse("/scala-uri?p1=one&p2=2&p3=true")
// Results in /scala-uri?p2=2
uri.filterQuery {
case (n, v) => n.contains("2") && v.contains("2")
}
uri.filterQuery(_._1 == "p1") // Results in /scala-uri?p1=oneThe filterQueryNames and filterQueryValues provide a more convenient way to filter just by Query Parameter name or value
import io.lemonlabs.uri.Url
val uri = Url.parse("/scala-uri?p1=one&p2=2&p3=true")
uri.filterQueryNames(_ > "p1") // Results in /scala-uri?p2=2&p3=true
uri.filterQueryValues(_.length == 1) // Results in /scala-uri?p2=2The collectQuery method will transform the Query String of a URI by applying the specified PartialFunction to each
Query String Parameter. Any parameters not matched in the PartialFunction will be removed.
import io.lemonlabs.uri.Url
val uri = Url.parse("/scala-uri?p1=one&p2=2&p3=true")
// Results in /scala-uri?p1_map=one_map
uri.collectQuery {
case ("p1", Some(v)) => ("p1_map", Some(v + "_map"))
}import io.lemonlabs.uri.Url
val absoluteUrl = Url.parse("http://www.example.com/example?a=b")
absoluteUrl.toRelativeUrl // This is /example?a=bimport io.lemonlabs.uri.Url
val relativeUrl = Url.parse("/example?a=b")
relativeUrl.withScheme("http").withHost("www.example.com") // This is http://www.example.com/example?a=bIt is possible to print out redacted URLs to logs with sensitive information either removed or replaced with a placeholder
Replacing with a placeholder:
import io.lemonlabs.uri._
import io.lemonlabs.uri.redact._
val url = Url.parse("http://user:[email protected]?secret=123&last=yes")
// This returns http://xxx:[email protected]?secret=xxx&last=yes
url.toRedactedString(Redact.withPlaceholder("xxx").params("secret", "other").user().password())Removing:
import io.lemonlabs.uri._
import io.lemonlabs.uri.redact._
val url = Url.parse("http://user:[email protected]?secret=123&other=true")
// This returns http://example.com
url.toRedactedString(Redact.byRemoving.allParams().userInfo())By default scala-uri only considers Urls equal if query parameters are in the same order:
import io.lemonlabs.uri._
val urlOne = Url.parse("https://example.com?a=1&b=2")
val urlTwo = Url.parse("https://example.com?b=2&a=1")
urlOne == urlTwo // this is false
val urlThree = Url.parse("https://example.com?a=1&b=2")
urlOne == urlThree // this is trueFor use-cases where query parameter order is not important, the equalsUnordered can be used
urlOne.equalsUnordered(urlTwo) // this is trueWhen using cats for equality testing, parameter order will also be considered by default
import cats.implicits._
urlOne === urlTwo // this is false
urlOne === urlThree // this is trueWith cats, query parameter order can be ignored for equality checks with the following import:
import io.lemonlabs.uri.Url.unordered._
urlOne === urlTwo // this is true
urlOne === urlThree // this is trueNote: depending on the type you are comparing, you will need to import a different cats Eq instance.
The following are available:
import io.lemonlabs.uri.Uri.unordered._
import io.lemonlabs.uri.Url.unordered._
import io.lemonlabs.uri.RelativeUrl.unordered._
import io.lemonlabs.uri.UrlWithAuthority.unordered._
import io.lemonlabs.uri.ProtocolRelativeUrl.unordered._
import io.lemonlabs.uri.AbsoluteUrl.unordered._
import io.lemonlabs.uri.UrlWithoutAuthority.unordered._
import io.lemonlabs.uri.SimpleUrlWithoutAuthority.unordered._
import io.lemonlabs.uri.QueryString.unordered._import io.lemonlabs.uri._
val uri: Uri = Uri.parse("...")
uri match {
case Uri(path) => // Matches Urns and Urls
case Urn(path) => // Matches Urns
case Url(path, query, fragment) => // Matches Urls
case RelativeUrl(path, query, fragment) => // Matches RelativeUrls
case UrlWithAuthority(authority, path, query, fragment) => // Matches AbsoluteUrl and ProtocolRelativeUrl
case AbsoluteUrl(scheme, authority, path, query, fragment) => // Matches AbsoluteUrl
case ProtocolRelativeUrl(authority, path, query, fragment) => // Matches ProtocolRelativeUrl
case UrlWithoutAuthority(scheme, path, query, fragment) => // Matches UrlWithoutAuthorityUrl
case DataUrl(mediaType, base64, data) => // Matches DataUrl
case ScpLikeUrl(user, host, path) => // Matches ScpLikeUrl
}In some cases scalac will be able to detect instances where not all cases are being matched. For example:
import io.lemonlabs.uri._
Uri.parse("/test") match {
case u: Url => println(u.toString)
}results in the following compiler warning, because Uri.parse can return Urns as well as Urls:
<console>:15: warning: match may not be exhaustive.
It would fail on the following input: Urn(_)
In this instance, using Url.parse instead of Uri.parse would fix this warning
You can parse a String representing the host part of a URI with Host.parse. The return type is Host with an
underling implementation of DomainName, IpV4 or IpV6.
import io.lemonlabs.uri.Host
val host = Host.parse("lemonlabs.io")import io.lemonlabs.uri.{IpV4, IpV6}
val ipv4 = IpV4.parse("13.32.214.142")
val ipv6 = IpV6.parse("[1:2:3:4:5:6:7:8]")import io.lemonlabs.uri._
val host: Host = Host.parse("...")
host match {
case Host(host) => // Matches DomainNames, IpV4s and IpV6s
case DomainName(host) => // Matches DomainNames
case ip: IpV4 => // Matches IpV4s
case ip: IpV6 => // Matches IpV6s
}import io.lemonlabs.uri._
val path: Path = Path.parse("...")
path match {
case Path(parts) => // Matches any path
case AbsolutePath(parts) => // Matches any path starting with a slash
case RootlessPath(parts) => // Matches any path that *doesn't* start with a slash
case PathParts("a", "b", "c") => // Matches "/a/b/c" and "a/b/c"
case PathParts("a", "b", _*) => // Matches any path starting with "/a/b" or "a/b"
case EmptyPath() => // Matches ""
case PathParts() => // Matches "" and "/"
case UrnPath("nid", "nss") => // Matches a URN Path "nid:nss"
}By Default, scala-uri will URL percent encode paths and query string parameters. To prevent this, you can call the uri.toStringRaw method:
import io.lemonlabs.uri.Url
val uri = Url.parse("http://example.com/path with space?param=üri")
uri.toString // This is: http://example.com/path%20with%20space?param=%C3%BCri
uri.toStringRaw // This is: http://example.com/path with space?param=üriThe characters that scala-uri will percent encode by default can be found here. You can modify which characters are percent encoded like so:
Only percent encode the hash character:
import io.lemonlabs.uri.config.UriConfig
import io.lemonlabs.uri.encoding._
implicit val config: UriConfig = UriConfig(encoder = percentEncode('#'))Percent encode all the default chars, except the plus character:
import io.lemonlabs.uri.config.UriConfig
import io.lemonlabs.uri.encoding._
implicit val config: UriConfig = UriConfig(encoder = percentEncode -- '+')Encode all the default chars, and also encode the letters a and b:
import io.lemonlabs.uri.config.UriConfig
import io.lemonlabs.uri.encoding._
implicit val config: UriConfig = UriConfig(encoder = percentEncode ++ ('a', 'b'))The default behaviour with scala-uri, is to encode spaces as + in the querystring and as %20 elsewhere in the URL.
If you instead wish spaces to be encoded as %20 in the query, then simply add the following implicit val to your code:
import io.lemonlabs.uri.Url
import io.lemonlabs.uri.config.UriConfig
import io.lemonlabs.uri.encoding._
import io.lemonlabs.uri.encoding.PercentEncoder._
implicit val config: UriConfig = UriConfig.default.copy(queryEncoder = PercentEncoder())
val uri = Url.parse("http://theon.github.com?test=uri with space")
uri.toString // This is http://theon.github.com?test=uri%20with%20spaceThe default behaviour with scala-uri, is to decode + in query string parameters to spaces and to leave it as a literal + elsewhere in the URL.
If you instead wish + to be left as + in the query, then simply add the following implicit val to your code:
import io.lemonlabs.uri.Url
import io.lemonlabs.uri.config.UriConfig
import io.lemonlabs.uri.decoding._
implicit val config: UriConfig = UriConfig.default.copy(queryDecoder = PercentDecoder)
val uri = Url.parse("http://theon.github.com?test=uri+with+plus")
uri.query.param("test") // This is Some("uri+with+plus")If you would like to do some custom encoding for specific characters, you can use the encodeCharAs encoder.
import io.lemonlabs.uri.Url
import io.lemonlabs.uri.config.UriConfig
import io.lemonlabs.uri.encoding._
implicit val config: UriConfig = UriConfig(encoder = percentEncode + encodeCharAs(' ', "_"))
val uri = Url.parse("http://theon.github.com/uri with space")
uri.toString // This is http://theon.github.com/uri_with_spaceBy Default, scala-uri will URL percent decode paths and query string parameters during parsing:
import io.lemonlabs.uri.Url
val uri = Url.parse("http://example.com/i-have-%25been%25-percent-encoded")
uri.toString // This is: http://example.com/i-have-%25been%25-percent-encoded
uri.toStringRaw // This is: http://example.com/i-have-%been%-percent-encodedTo prevent this, you can bring the following implicit into scope:
import io.lemonlabs.uri.Url
import io.lemonlabs.uri.config.UriConfig
import io.lemonlabs.uri.decoding.NoopDecoder
implicit val c: UriConfig = UriConfig(decoder = NoopDecoder)
val uri = Url.parse("http://example.com/i-havent-%been%-percent-encoded")
uri.toString // This is: http://example.com/i-havent-%25been%25-percent-encoded
uri.toStringRaw // This is: http://example.com/i-havent-%been%-percent-encodedIf your Uri contains invalid percent encoding, by default scala-uri will throw a UriDecodeException:
Url.parse("/?x=%3") // This throws a UriDecodeExceptionYou can configure scala-uri to instead ignore invalid percent encoding and only percent decode correctly percent encoded values like so:
import io.lemonlabs.uri.Url
import io.lemonlabs.uri.config.UriConfig
import io.lemonlabs.uri.decoding.PercentDecoder
implicit val c: UriConfig = UriConfig(
decoder = PercentDecoder(ignoreInvalidPercentEncoding = true)
)
val uri = Url.parse("/?x=%3")
uri.toString // This is /?x=%253
uri.toStringRaw // This is /?x=%3If you wish to replace all existing query string parameters with a given name, you can use the Url.replaceParams() method:
import io.lemonlabs.uri.Url
val uri = Url.parse("http://example.com/path?param=1")
val newUri = uri.replaceParams("param", "2")
newUri.toString // This is: http://example.com/path?param=2If you wish to remove all existing query string parameters with a given name, you can use the uri.removeParams() method:
import io.lemonlabs.uri.Url
val uri = Url.parse("http://example.com/path?param=1¶m2=2")
val newUri = uri.removeParams("param")
newUri.toString // This is: http://example.com/path?param2=2scala-uri has support for not rendering query parameters that have a value of None. Set renderQuery = ExcludeNones
in your UriConfig and make it visible in the scope where you parse/create your Url
import io.lemonlabs.uri.Url
import io.lemonlabs.uri.config._
implicit val config: UriConfig = UriConfig(renderQuery = ExcludeNones)
val url = Url.parse("http://github.com/lemonlabsuk").addParams("a" -> Some("some"), "b" -> None)
url.toString // This is http://github.com/lemonlabsuk?a=someTo get the query string parameters as a Map[String,Seq[String]] you can do the following:
import io.lemonlabs.uri.Url
val uri = Url.parse("http://example.com/path?a=b&a=c&d=e")
uri.query.paramMap // This is: Map("a" -> Vector("b", "c"), "d" -> Vector("e"))scala-uri supports user information (username and password) encoded in URLs.
Parsing URLs with user information:
import io.lemonlabs.uri.Url
val url = Url.parse("http://user:[email protected]")
url.user // This is Some("user")
url.password // This is Some("pass")Modifying user information:
import io.lemonlabs.uri.AbsoluteUrl
val url = AbsoluteUrl.parse("http://host.com")
url.withUser("jack") // URL is now http://[email protected]import io.lemonlabs.uri.AbsoluteUrl
val url = AbsoluteUrl.parse("http://user:[email protected]")
url.withPassword("secret") // URL is now http://user:[email protected]Note: that using clear text passwords in URLs is ill advised
Protocol Relative URLs are supported in scala-uri. A Uri object with a protocol of None, but a host of Some(x) will be considered a protocol relative URL.
import io.lemonlabs.uri.Url
val uri = Url.parse("//example.com/path") // Return type is Url
uri.schemeOption // This is: None
uri.hostOption // This is: Some("example.com")Use ProtocolRelativeUrl.parse if you know your URL will always be Protocol Relative:
import io.lemonlabs.uri.ProtocolRelativeUrl
val uri = ProtocolRelativeUrl.parse("//example.com/path") // Return type is ProtocolRelativeUrl
uri.schemeOption // This is: None
uri.host // This is: "example.com"By default scala-uri uses UTF-8 charset encoding:
import io.lemonlabs.uri.Url
val uri = Url.parse("http://theon.github.com/uris-in-scala.html?chinese=网址")
uri.toString // This is http://theon.github.com/uris-in-scala.html?chinese=%E7%BD%91%E5%9D%80This can be changed like so:
import io.lemonlabs.uri.config.UriConfig
import io.lemonlabs.uri.Url
implicit val conf: UriConfig = UriConfig(charset = "GB2312")
val uri = Url.parse("http://theon.github.com/uris-in-scala.html?chinese=网址")
uri.toString // This is http://theon.github.com/uris-in-scala.html?chinese=%CD%F8%D6%B7import io.lemonlabs.uri.Url
// This returns Some("www")
Url.parse("http://www.example.com/blah").subdomain
// This returns Some("a.b.c")
Url.parse("http://a.b.c.example.com/blah").subdomain
// This returns None
Url.parse("http://example.com/blah").subdomain
// This returns Vector("a", "a.b", "a.b.c", "a.b.c.example")
Url.parse("http://a.b.c.example.com/blah").subdomains
// This returns Some("a")
Url.parse("http://a.b.c.example.com/blah").shortestSubdomain
// This returns Some("a.b.c.example")
Url.parse("http://a.b.c.example.com/blah").longestSubdomainThese methods return None or Vector.empty for URLs without a Host (e.g. Relative URLs)
The method apexDomain returns the apex domain
for the URL (e.g. example.com for http://www.example.com/path)
import io.lemonlabs.uri.Url
val uri = Url.parse("http://www.google.co.uk/blah")
uri.apexDomain // This returns Some("google.co.uk")scala-uri uses the list of public suffixes from publicsuffix.org to allow you to identify
the TLD of your absolute URIs.
The publicSuffix method returns the longest public suffix from your URI
import io.lemonlabs.uri.Url
val uri = Url.parse("http://www.google.co.uk/blah")
uri.publicSuffix // This returns Some("co.uk")The publicSuffixes method returns all the public suffixes from your URI
import io.lemonlabs.uri.Url
val uri = Url.parse("http://www.google.co.uk/blah")
uri.publicSuffixes // This returns Vector("co.uk", "uk")These methods return None and Vector.empty, respectively for URLs without a Host (e.g. Relative URLs)
See RFC 3490
import io.lemonlabs.uri.Url
val url = Url.parse("https://はじめよう.みんな/howto.html")
url.toStringPunycode // This returns "https://xn--p8j9a0d9c9a.xn--q9jyb4c/howto.html"Mailto URLs are best parsed with UrlWithoutAuthority.parse, but can also be parsed with Url.parse
import io.lemonlabs.uri.UrlWithoutAuthority
val mailto = UrlWithoutAuthority.parse("mailto:[email protected]?subject=Hello")
mailto.scheme // This is Some(mailto")
mailto.path // This is "[email protected]"
mailto.query.param("subject") // This is Some("Hello")Data URLs are defined in RFC2397
import java.io.ByteArrayInputStream
import io.lemonlabs.uri.DataUrl
import javax.imageio.ImageIO
// A data URL containing a PNG image of a red dot
val dataUrl = DataUrl.parse("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==")
dataUrl.scheme // This is "data"
dataUrl.mediaType.value // This is "image/png"
dataUrl.base64 // This is true
// Convert the image data to a java.awt.image.BufferedImage
val image = ImageIO.read(new ByteArrayInputStream(dataUrl.data))import io.lemonlabs.uri.DataUrl
val dataUrl = DataUrl.parse("data:text/plain;charset=UTF-8;page=21,the%20data:1234,5678")
dataUrl.mediaType.value // This is text/plain
dataUrl.mediaType.charset // This is UTF-8
dataUrl.mediaType.parameters // This is Vector("charset" -> "UTF-8", "page" -> "21")
dataUrl.base64 // This is false
dataUrl.dataAsString // This is "the data:1234,5678"git/scp style URLs can be parsed like so:
import io.lemonlabs.uri.ScpLikeUrl
val url = ScpLikeUrl.parse("[email protected]:lemonlabsuk/scala-uri.git")
url.user // This is Some("git")
url.host.toString // This is "github.com"
url.path.toString // This is "lemonlabsuk/scala-uri.git"Note that ScpLikeUrl.parse, should only be used for git URLs with scp-like syntax (with a :
between the host and path). For all other git URLs, AbsoluteUrl.parse or Url.parse should
be used:
import io.lemonlabs.uri.AbsoluteUrl
val gitUrl = AbsoluteUrl.parse("git://github.com/lemonlabsuk/scala-uri.git")
val gitSshUrl = AbsoluteUrl.parse("git+ssh://[email protected]/lemonlabsuk/scala-uri.git")
val sshUrl = AbsoluteUrl.parse("ssh://[email protected]/lemonlabsuk/scala-uri.git")
val httpsUrl = AbsoluteUrl.parse("https://github.com/lemonlabsuk/scala-uri.git")The version of DSL which relies on the types to render urls providing better control over the way values would be translated to url parts.
It is possible to use arbitrary types as parts of the url:
import io.lemonlabs.uri.typesafe._
import io.lemonlabs.uri.typesafe.dsl._
final case class Foo(a: Int, b: String)
object Foo {
implicit val traversableParams: TraversableParams[Foo] = TraversableParams.product
}
val uri = "http://theon.github.com/scala-uri" addParams Foo(a = 1, b = "bar")
uri.toString //This is: http://theon.github.com/scala-uri?a=1&b=barimport io.lemonlabs.uri.typesafe._
import io.lemonlabs.uri.typesafe.dsl._
sealed trait Bar {
def name: String
}
case object A extends Bar {
val name: String = "A"
}
case object B extends Bar {
val name: String = "B"
}
object Bar {
implicit val queryValue: QueryValue[Bar] = QueryValue.derive[Bar].by(_.name)
}
val uri = "http://theon.github.com/scala-uri" ? ("foo" -> A)
uri.toString //This is: http://theon.github.com/scala-uri?foo=Aimport io.lemonlabs.uri.typesafe._
import io.lemonlabs.uri.typesafe.dsl._
final case class Foo(a: String, b: Int)
object Foo {
implicit val pathPart: PathPart[Foo] = (foo: Foo) => s"${foo.a}/${foo.b}"
}
val uri = "http://theon.github.com" / "scala-uri" / Foo(a = "user", b = 1)
uri.toString //This is: http://theon.github.com/scala-uri/user/1import io.lemonlabs.uri.typesafe._
import io.lemonlabs.uri.typesafe.dsl._
final case class Foo(a: String, b: Int)
object Foo {
implicit val fragment: Fragment[Foo] = (foo: Foo) => Some(s"${foo.a}-${foo.b}")
}
val uri5 = "http://theon.github.com/scala-uri" `#` Foo(a = "user", b = 1)
uri5.toString //This is: http://theon.github.com/scala-uri#user-1See scala-uri-scalajs-example for usage
scala-uri provides type class instances of cats.Eq, cats.Show and cats.Order for:
Uri , Url, RelativeUrl, UrlWithAuthority, ProtocolRelativeUrl, AbsoluteUrl,
UrlWithoutAuthority, SimpleUrlWithoutAuthority, DataUrl, ScpLikeUrl, Urn, Authority, UserInfo,
Host, DomainName, IpV4, IpV6, MediaType, Path, UrlPath, AbsoluteOrEmptyPath,
RootlessPath, AbsolutePath, UrnPath, QueryString
The type class instances exist in the companion objects for these types.
scala-uri 4.x.x is currently built with support for Scala 3, Scala 2.13.x, Scala 2.12.x and Scala.js 1.1.0+
scala-uri 3.x.x is currently built with support for Scala 2.13.x, Scala 2.12.x and Scala.js 1.1.0+
- For
2.11.xsupport usescala-uri1.4.10from branch1.4.x - For
2.10.xsupport usescala-uri0.4.17from branch0.4.x - For
2.9.xsupport usescala-uri0.3.6from branch0.3.x - For Scala.js
1.x.xsupport, usescala-uri4.0.0 - For Scala.js
0.6.xsupport, usescala-uri2.2.3
Release builds are available in maven central. For SBT users just add the following dependency:
"io.lemonlabs" %% "scala-uri" % "4.0.3"For maven users you should use (for 2.13.x):
<dependency>
<groupId>io.lemonlabs</groupId>
<artifactId>scala-uri_2.13</artifactId>
<version>4.0.3</version>
</dependency>Contributions to scala-uri are always welcome. Check out the Contributing Guidelines
- Scala 3 support has been added. Scala 2.13 and 2.12 support remain
- Binary Incompatibility: 4765b4e
removed a single
UriConfig.copy()overload. This overload existed only to maintain binary compatibility with an older version of scala-uri Use the remainingcopy()method onUriConfiginstead - Binary Incompatibility: 4765b4e
removed a single
UriConfigoverloaded constructor. This overload existed only to maintain binary compatibility with an older version of scala-uri Use the remaining constructor orapplymethod remaining onUriConfiginstead
- Backwards Incompatible: The space character is now encoded to
+instead of%20in query string parameters by default. - Backwards Incompatible: The
+method inio.lemonlabs.uri.encoding.UriEncoder, now chains encoders in the opposite order to be more intuitive. E.g.a + bwill encode with encoderafirst, followed by encoderb - Binary Incompatibility: The following deprecated classes have now been removed:
io.lemonlabs.uri.inet.PublicSuffixTrieio.lemonlabs.uri.inet.Trieio.lemonlabs.uri.dsl.*
- Binary Incompatibility:
Url.addParam("key", "value")andUrl.addParam("key" -> "value")now has a return typeSelfrather thanUrl. E.g.AbsoluteUrl.addParamnow has a return type ofAbsoluteUrlandRelativeUrl.addParamhas a return type ofRelativeUrl
- scala-uri no longer depends on a JSON library.
- Binary Incompatibility: The case class
UrlWithoutAuthorityhas been renamedSimpleUrlWithoutAuthority. There is now a trait calledUrlWithoutAuthority. This trait has a companion object withapply,unapplyandparsemethods, so it mostly can be used in the same way as the previous case class. - Binary Incompatibility: Parsing a Data URL will now return an instance of
DataUrlrather thanUrlWithoutAuthority - Binary Incompatibility:
UserInfo.useris now of typeStringrather thanOption[String] - Binary Incompatibility:
Authority.userInfois now of typeOption[UserInfo] - Binary Incompatibility:
UserInfo.emptymethod removed - Binary Incompatibility:
QueryString.fromPairOptionsremoved. UseQueryString.fromPairsinstead. - Binary Incompatibility:
Url.withQueryStringOptionValuesremoved. UsewithQueryStringinstead. - Binary Incompatibility:
Url.addParamsOptionValuesandQueryString.addParamsOptionValueshave been renames toaddParams TypesafeUrlDsl- Binary Incompatibility:
withParams[A: TraversableParams](params: A)renamed toaddParams /(PathPart)no longer splits the part by slash. If you want to add multiple path parts use/(TraversablePathParts)instead
- Binary Incompatibility:
- Type Classes
- Binary Incompatibility:
Fragment[A].fragmentreturnsOption[String]rather thanString - Binary Incompatibility:
Url.withFragmentnow takes argument of typeT: Fragmentrather thanStringandOption[String]Type Class instances are provided the method can be used withStringandOption[String]values just as before - Binary Incompatibility:
Url.addPathPartsandUrl.addPathPartnow takes arguments of typeP: TraversablePathPartsorP: PathPartrather thanIterable[String]orStringType Class instances are provided the methods can be used withStringandIterable[String]values just as before - Binary Incompatibility:
Url.withQueryString,Url.addParam,Url.addParams,Url.replaceParams,Url.removeParams,Url.mapQuery,Url.flatMapQuery,Url.collectQuery,Url.mapQueryNamesandUrl.mapQueryValuesnow takes argument of typeKV: QueryKeyValue,K: QueryKeyorV: QueryValuerather than(String, String)orStringType Class instances are provided the methods can be used with(String, String)orStringvalues just as before
- Binary Incompatibility:
- The URL builder DSL has been deprecated in favour of the Typesafe URL builder DSL
Authority.parseno longer expects it's string argument to start with//, as this is not part of the Authority, it is a delimiter. See RFC 3986UserInfo.parseno longer expects it's string argument to end with a@, as this is not part of the UserInfo, it is a delimiter. See RFC 3986QueryString.toStringno longer returns a leading?, as this is not part of the query string, it is a delimiter. See RFC 3986- Forward slashes in paths are now percent encoded by default.
This means
Url.parse("/%2F/").toStringreturns"/%2F/"rather than///in previous versions To return to the previous behavior, you can bring aUriConfiglike so into scopeimport io.lemonlabs.uri.encoding.PercentEncoder._ implicit val c = UriConfig.default.copy(pathEncoder = PercentEncoder(PATH_CHARS_TO_ENCODE - '/'))
- scala 2.11 support dropped, please upgrade to 2.12 or 2.13
Thanks to @evanbennett. 1.x.x is inspired by his fork here
and discussion here.
- Package change from
com.netaporter.uritoio.lemonlabs.uri - The single
Uricase class has now been replaced with a class hierarchy. Use the most specific class in this hierarchy that fits your use case Uriused to be a case class, but the replacementsUriandUrlare now traits. This means they no longer have acopymethod. Use thewithmethods instead (e.g.withHost,withPathetc)hostmethod onUrlnow has return typeHostrather thanString. You may have to changeurl.hosttourl.host.toStringpathmethod onUrlnow has return typePathrather thanString. You may have to changeurl.pathtourl.path.toString- Changed parameter value type from
AnytoStringin methodsaddParam,addParams,replaceParams. Please now call.toStringbefore passing non String types to these methods - Changed parameter value type from
Option[Any]toOption[String]in methodreplaceAll. Please now call.toStringbefore passing non String types to this method - Query string parameters with a value of
Nonewill now be rendered with no equals sign by default (e.g.?param). Previously some methods (such as?,&,\?,addParamandaddParams) would not render parameters with a value ofNoneat all. In 1.x.x, this behaviour can be achieved by using therenderQueryconfig option. - In most cases
Url.parseshould be used instead ofUri.parse. See all parse methods here schemeis now calledschemeOptiononUri. If you have an instance ofAbsoluteUrlorProtocolRelativeUrlthere is stillschememethod but it returnsStringrather thanOption[String]protocolmethod has been removed fromUri. UseschemeOptioninstead- Type changed from
SeqtoVectorfor:subdomains,publicSuffixes,paramsreturn typeremoveAllandremoveParamsargument typesparamsfield inQueryStringparamMapandpathPartsfields inUri, nowUrl
- Methods
addParamandaddParamsthat took Option arguments are now calledaddParamOptionValueandaddParamsOptionValues - Method
replaceAllParamshas been replaced withwithQueryStringorwithQueryStringOptionValues - Method
removeAllParamshas been replaced withwithQueryString(QueryString.empty) - Method
subdomainhas been removed from the Scala.js version. The implementation was incorrect and did not match the JVM version ofsubdomain. Once public suffixes are supported for the Scala.js version, a correct implementation ofsubdomaincan be added - Implicit
UriConfigs now need to be where yourUris are parsed/constructed, rather than where they are rendered - Method
hostPartshas been removed fromUri. This method predatedpublicSuffixandsubdomainwhich are more useful methods for pulling apart a host - Field
pathStartsWithSlashremoved fromUri. This was only intended to be used internally. You can now instead check ifUri.pathis an instance ofAbsolutePathto determine if the path will start with slash
- Matrix parameters have been removed. If you still need this, raise an issue
- scala 2.10 support dropped, please upgrade to 2.11 or 2.12 to use scala-uri 0.5.x
- Scala.js support added
- Package changes / import changes
- All code moved from
com.github.theonpackage tocom.netaporterpackage scala-urihas been organised into the following packages:encoding,decoding,configanddsl. You will need to update import statments.- Name changes
PermissiveDecoderrenamed toPermissivePercentDecoderQueryStringandMatrixParamsconstructor argumentparametersshortened toparamsUri.parseUrirenamed toUri.parseprotocolconstructor arg inUrirenamed toschemeQuerystringrenamed toQueryString- Query String constructor argument
parameterschanged type fromMap[String, List[String]]toSeq[(String,String)] Uriconstructor argumentpathPartschanged type fromListtoVectorUrimethod to add query string parameters renamed fromparamstoaddParams. Same withmatrixParams->addMatrixParamsPercentEncoderDefaultsobject renamed toPercentEncodercompanion object.- Copy methods
user/password/port/host/schemenow all prefixed withwith, e.g.withHost - New
UriConfigcase class used to specify encoders, decoders and charset to be used. See examples in Custom encoding, URL Percent Decoding and Character Sets
scala-uri is open source software released under the Apache 2 License.