Skip to content

Commit

Permalink
Merge branch 'master' into update/scala-library-2.12.13
Browse files Browse the repository at this point in the history
  • Loading branch information
theon authored Apr 29, 2021
2 parents 42fb930 + b42a08f commit cda1bfc
Show file tree
Hide file tree
Showing 11 changed files with 390 additions and 19 deletions.
14 changes: 13 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,23 @@
---
language: scala

install:
- |
# From: https://eed3si9n.com/sbt-1.4.9
# Remove this when https://github.com/travis-ci/travis-build/pull/1980 is fixed
export SBT_LAUNCHER=1.4.9
export SBT_OPTS="-Dfile.encoding=UTF-8"
curl -L --silent "https://github.com/sbt/sbt/releases/download/v$SBT_LAUNCHER/sbt-$SBT_LAUNCHER.tgz" > $HOME/sbt.tgz
tar zxf $HOME/sbt.tgz -C $HOME
sudo rm /usr/local/bin/sbt
sudo ln -s $HOME/sbt/bin/sbt /usr/local/bin/sbt
script: >
sbt $COVERAGE ++$TRAVIS_SCALA_VERSION check mdoc coverage $PROJECT/test coverageReport &&
cd target &&
git clone https://github.com/lemonlabsuk/scala-uri-demo.git &&
cd scala-uri-demo &&
sbt -Dscala.ver=$TRAVIS_SCALA_VERSION -Dscala.uri.ver=3.1.0 test &&
sbt -Dscala.ver=$TRAVIS_SCALA_VERSION -Dscala.uri.ver=3.2.0 test &&
cd "$TRAVIS_BUILD_DIR"
jdk:
Expand Down
68 changes: 61 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
# scala-uri

[![Build Status](https://travis-ci.org/lemonlabsuk/scala-uri.svg?branch=master)](https://travis-ci.org/lemonlabsuk/scala-uri)
[![Build Status](https://api.travis-ci.org/lemonlabsuk/scala-uri.svg?branch=master)](https://travis-ci.org/lemonlabsuk/scala-uri)
[![codecov.io](http://codecov.io/github/lemonlabsuk/scala-uri/coverage.svg?branch=master)](https://codecov.io/gh/lemonlabsuk/scala-uri/branch/master)
[![Slack](https://lemonlabs.io/slack/badge.svg)](https://lemonlabs.io/slack)
[![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.lemonlabs/scala-uri_2.13/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.lemonlabs/scala-uri_2.12)
[![Scala.js](https://www.scala-js.org/assets/badges/scalajs-1.0.0.svg)](#scalajs-support)
[![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/lemonlabsuk/scala-uri.svg)](http://isitmaintained.com/project/lemonlabsuk/scala-uri "Average time to resolve an issue")
[![Percentage of issues still open](http://isitmaintained.com/badge/open/lemonlabsuk/scala-uri.svg)](http://isitmaintained.com/project/lemonlabsuk/scala-uri "Percentage of issues still open")

[![Cats Friendly Badge](https://typelevel.org/cats/img/cats-badge-tiny.png)](#cats-support)

`scala-uri` is a small Scala library that helps you work with URIs. It has the following features:

* A [RFC 3986](https://www.ietf.org/rfc/rfc3986.txt) compliant [parser](#parsing) to parse URLs and URNs from Strings
Expand All @@ -33,7 +31,7 @@
To include it in your SBT project from maven central:

```scala
"io.lemonlabs" %% "scala-uri" % "3.1.0"
"io.lemonlabs" %% "scala-uri" % "3.2.0"
```

## Migration Guides
Expand Down Expand Up @@ -218,6 +216,62 @@ val url = Url.parse("http://user:[email protected]?secret=123&other=true")
url.toRedactedString(Redact.byRemoving.allParams().userInfo())
```

## Url Equality

By default scala-uri only considers `Url`s equal if query parameters are in the same order:

```scala mdoc:reset
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 true
```

For use-cases where query parameter order is not important, the `equalsUnordered` can be used

```scala mdoc
urlOne.equalsUnordered(urlTwo) // this is true
```

When using cats for equality testing, parameter order will also be considered by default

```scala mdoc
import cats.implicits._

urlOne === urlTwo // this is false
urlOne === urlThree // this is true
```

With cats, query parameter order can be ignored for equality checks with the following import:

```scala mdoc
import io.lemonlabs.uri.Url.unordered._

urlOne === urlTwo // this is true
urlOne === urlThree // this is true
```

Note: depending on the type you are comparing, you will need to import a different cats `Eq` instance.
The following are available:

```scala mdoc:reset
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._
```

## Pattern Matching URIs

```scala mdoc:reset
Expand Down Expand Up @@ -844,13 +898,13 @@ The type class instances exist in the companion objects for these types.
* For `2.11.x` support use `scala-uri` `1.4.10` from branch [`1.4.x`](https://github.com/lemonlabsuk/scala-uri/tree/1.4.x)
* For `2.10.x` support use `scala-uri` `0.4.17` from branch [`0.4.x`](https://github.com/lemonlabsuk/scala-uri/tree/0.4.x)
* For `2.9.x` support use `scala-uri` `0.3.6` from branch [`0.3.x`](https://github.com/lemonlabsuk/scala-uri/tree/0.3.x)
* For Scala.js `1.x.x` support, use `scala-uri` `3.1.0`
* For Scala.js `1.x.x` support, use `scala-uri` `3.2.0`
* For Scala.js `0.6.x` support, use `scala-uri` `2.2.3`

Release builds are available in maven central. For SBT users just add the following dependency:

```scala
"io.lemonlabs" %% "scala-uri" % "3.1.0"
"io.lemonlabs" %% "scala-uri" % "3.2.0"
```

For maven users you should use (for 2.13.x):
Expand All @@ -859,7 +913,7 @@ For maven users you should use (for 2.13.x):
<dependency>
<groupId>io.lemonlabs</groupId>
<artifactId>scala-uri_2.13</artifactId>
<version>3.1.0</version>
<version>3.2.0</version>
</dependency>
```

Expand Down
13 changes: 7 additions & 6 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ val sharedSettings = Seq(
organization := "io.lemonlabs",
libraryDependencies ++= Seq(
"org.typelevel" %%% "simulacrum-scalafix-annotations" % "0.5.4",
"org.scalatest" %%% "scalatest" % "3.2.6" % Test,
"org.scalatest" %%% "scalatest" % "3.2.8" % Test,
"org.scalatestplus" %%% "scalacheck-1-14" % "3.2.2.0" % Test,
"org.scalacheck" %%% "scalacheck" % "1.15.3" % Test,
"org.typelevel" %%% "cats-laws" % "2.4.2" % Test
"org.typelevel" %%% "cats-laws" % "2.6.0" % Test
),
scalacOptions := Seq(
"-unchecked",
Expand Down Expand Up @@ -61,9 +61,9 @@ val scalaUriSettings = Seq(
name := "scala-uri",
description := "Simple scala library for building and parsing URIs",
libraryDependencies ++= Seq(
"org.parboiled" %%% "parboiled" % "2.2.1",
"com.chuusai" %%% "shapeless" % "2.3.3",
"org.typelevel" %%% "cats-core" % "2.4.2"
"org.parboiled" %%% "parboiled" % "2.3.0",
"com.chuusai" %%% "shapeless" % "2.3.4",
"org.typelevel" %%% "cats-core" % "2.6.0"
),
pomPostProcess := { node =>
new RuleTransformer(new RewriteRule {
Expand Down Expand Up @@ -117,7 +117,8 @@ val previousVersions = (0 to 0).map(v => s"3.$v.0").toSet

val mimaExcludes = Seq(
ProblemFilters.exclude[ReversedMissingMethodProblem]("io.lemonlabs.uri.typesafe.QueryValueInstances1.*"),
ProblemFilters.exclude[ReversedMissingMethodProblem]("io.lemonlabs.uri.Url.*")
ProblemFilters.exclude[ReversedMissingMethodProblem]("io.lemonlabs.uri.Url.*"),
ProblemFilters.exclude[ReversedMissingMethodProblem]("io.lemonlabs.uri.Uri.*")
)

val mimaSettings = Seq(
Expand Down
2 changes: 1 addition & 1 deletion project/build.properties
Original file line number Diff line number Diff line change
@@ -1 +1 @@
sbt.version=1.4.7
sbt.version=1.5.1
6 changes: 3 additions & 3 deletions project/plugins.sbt
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.6.1")
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.7.0")

addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.0.0")

addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.5.0")
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.5.1")

addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.1.2")

Expand All @@ -12,4 +12,4 @@ addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.2")

addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.2.18")

addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.26")
addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.27")
10 changes: 10 additions & 0 deletions shared/src/main/scala/io/lemonlabs/uri/QueryString.scala
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,11 @@ case class QueryString(params: Vector[(String, Option[String])])(implicit config
else paramsAsString.mkString("&")
}

private def paramsCardinality = params.groupBy(identity)

def equalsUnordered(other: QueryString): Boolean =
this.paramsCardinality == other.paramsCardinality

/** Returns the query string with no encoding taking place (e.g. non ASCII characters will not be percent encoded)
* @return String containing the raw query string for this Uri
*/
Expand Down Expand Up @@ -263,4 +268,9 @@ object QueryString {
implicit val eqQueryString: Eq[QueryString] = Eq.fromUniversalEquals
implicit val showQueryString: Show[QueryString] = Show.fromToString
implicit val orderQueryString: Order[QueryString] = Order.by(_.params)

object unordered {
implicit val eqQueryString: Eq[QueryString] =
(x: QueryString, y: QueryString) => x.equalsUnordered(y)
}
}
81 changes: 81 additions & 0 deletions shared/src/main/scala/io/lemonlabs/uri/Uri.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import io.lemonlabs.uri.typesafe.PathPart.ops._
import io.lemonlabs.uri.typesafe.TraversablePathParts.ops._
import io.lemonlabs.uri.typesafe.Fragment.ops._

import java.util
import scala.util.Try

/** Represents a URI. See [[https://www.ietf.org/rfc/rfc3986 RFC 3986]]
Expand Down Expand Up @@ -71,6 +72,10 @@ sealed trait Uri extends Product with Serializable {
def toJavaURI: java.net.URI =
new java.net.URI(toString(config))

/** Similar to `==` but ignores the ordering of any query string parameters
*/
def equalsUnordered(other: Uri): Boolean

/** 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
*/
Expand Down Expand Up @@ -102,6 +107,11 @@ object Uri {
implicit val eqUri: Eq[Uri] = Eq.fromUniversalEquals
implicit val showUri: Show[Uri] = Show.fromToString
implicit val orderUri: Order[Uri] = Order.by(_.toString())

object unordered {
implicit val eqUri: Eq[Uri] =
(x: Uri, y: Uri) => x.equalsUnordered(y)
}
}

/** Represents a URL, which will be one of these forms:
Expand Down Expand Up @@ -476,6 +486,15 @@ sealed trait Url extends Uri {

def toRedactedString(redactor: Redactor)(implicit conf: UriConfig = UriConfig.default): String =
redactor.apply(this).toString(conf)

/** Similar to `==` but ignores the ordering of any query string parameters
*/
def equalsUnordered(other: Uri): Boolean = other match {
case otherUrl: Url =>
this.removeQueryString() == otherUrl.removeQueryString() && query.equalsUnordered(otherUrl.query)
case _ =>
false
}
}

object Url {
Expand Down Expand Up @@ -519,6 +538,11 @@ object Url {
implicit val eqUrl: Eq[Url] = Eq.fromUniversalEquals
implicit val showUrl: Show[Url] = Show.fromToString
implicit val orderUrl: Order[Url] = Order.by(_.toString())

object unordered {
implicit val eqUrl: Eq[Url] =
(x: Url, y: Url) => x.equalsUnordered(y)
}
}

/** Represents Relative URLs which do not contain an authority. Examples include:
Expand Down Expand Up @@ -602,6 +626,11 @@ object RelativeUrl {
implicit val orderRelUrl: Order[RelativeUrl] = Order.by { url =>
(url.path, url.query, url.fragment)
}

object unordered {
implicit val eqRelUrl: Eq[RelativeUrl] =
(x: RelativeUrl, y: RelativeUrl) => x.equalsUnordered(y)
}
}

/** Represents absolute URLs with an authority (i.e. URLs with a host), examples include:
Expand Down Expand Up @@ -768,6 +797,11 @@ object UrlWithAuthority {
implicit val eqUrlWithAuthority: Eq[UrlWithAuthority] = Eq.fromUniversalEquals
implicit val showUrlWithAuthority: Show[UrlWithAuthority] = Show.fromToString
implicit val orderUrlWithAuthority: Order[UrlWithAuthority] = Order.by(_.toString())

object unordered {
implicit val eqUrlWithAuthority: Eq[UrlWithAuthority] =
(x: UrlWithAuthority, y: UrlWithAuthority) => x.equalsUnordered(y)
}
}

/** Represents protocol relative URLs, for example: `//example.com`
Expand Down Expand Up @@ -827,6 +861,11 @@ object ProtocolRelativeUrl {
implicit val orderProtocolRelUrl: Order[ProtocolRelativeUrl] = Order.by { url =>
(url.authority, url.path, url.query, url.fragment)
}

object unordered {
implicit val eqProtocolRelUrl: Eq[ProtocolRelativeUrl] =
(x: ProtocolRelativeUrl, y: ProtocolRelativeUrl) => x.equalsUnordered(y)
}
}

/** Represents absolute URLs, for example: `http://example.com`
Expand Down Expand Up @@ -887,6 +926,11 @@ object AbsoluteUrl {
implicit val orderAbsUrl: Order[AbsoluteUrl] = Order.by { url =>
(url.scheme, url.authority, url.path, url.query, url.fragment)
}

object unordered {
implicit val eqAbsUrl: Eq[AbsoluteUrl] =
(x: AbsoluteUrl, y: AbsoluteUrl) => x.equalsUnordered(y)
}
}

/** Represents URLs that do not have an authority, for example:
Expand Down Expand Up @@ -940,6 +984,11 @@ object UrlWithoutAuthority {
implicit val orderUrlWithoutAuthority: Order[UrlWithoutAuthority] = Order.by { url =>
(url.scheme, url.path, url.query, url.fragment)
}

object unordered {
implicit val eqUrlWithoutAuthority: Eq[UrlWithoutAuthority] =
(x: UrlWithoutAuthority, y: UrlWithoutAuthority) => x.equalsUnordered(y)
}
}

/** Represents URLs that do not have an authority, for example: `mailto:example@example.com`
Expand Down Expand Up @@ -1009,6 +1058,11 @@ object SimpleUrlWithoutAuthority {
implicit val orderSimpleUrlWithoutAuthority: Order[SimpleUrlWithoutAuthority] = Order.by { url =>
(url.scheme, url.path, url.query, url.fragment)
}

object unordered {
implicit val eqSimpleUrlWithoutAuthority: Eq[SimpleUrlWithoutAuthority] =
(x: SimpleUrlWithoutAuthority, y: SimpleUrlWithoutAuthority) => x.equalsUnordered(y)
}
}

/** Represents URLs with the data scheme, for example: `data:text/plain;charset=UTF-8;page=21,the%20data:1234,5678`
Expand Down Expand Up @@ -1086,6 +1140,23 @@ final case class DataUrl(mediaType: MediaType, base64: Boolean, data: Array[Byte

private[uri] def toString(c: UriConfig): String =
scheme + ":" + pathString(c)

override def equals(obj: Any): Boolean = obj match {
case other: DataUrl =>
other.canEqual(this) &&
mediaType == other.mediaType &&
base64 == other.base64 &&
util.Arrays.equals(data, other.data)
case _ => false
}

override def hashCode(): Int =
41 * (41 * (41 + mediaType.hashCode()) + base64.hashCode()) + util.Arrays.hashCode(data)

/** For DataUrls this method is exactly the same as `==`
*/
override def equalsUnordered(other: Uri): Boolean =
this == other
}

object DataUrl {
Expand Down Expand Up @@ -1169,6 +1240,11 @@ final case class ScpLikeUrl(override val user: Option[String], override val host
// Don't do percent encoding. Can't find any reference to it being
user.fold("")(_ + "@") + hostToString(host) + ":" + path.toString(config.withNoEncoding)
}

/** For ScpLikeUrls this method is exactly the same as `==`
*/
override def equalsUnordered(other: Uri): Boolean =
this == other
}

object ScpLikeUrl {
Expand Down Expand Up @@ -1217,6 +1293,11 @@ final case class Urn(path: UrnPath)(implicit val config: UriConfig = UriConfig.d

private[uri] def toString(c: UriConfig): String =
scheme + ":" + path.toString(c)

/** For URNs this method is exactly the same as `==`
*/
def equalsUnordered(other: Uri): Boolean =
this == other
}

object Urn {
Expand Down
Loading

0 comments on commit cda1bfc

Please sign in to comment.