From b2c76b0360f93c77fe30961f5db55724f0dc7407 Mon Sep 17 00:00:00 2001 From: Jules Ivanic Date: Fri, 16 Aug 2024 08:25:34 +1000 Subject: [PATCH] Simplify the interface of `URL.decode` by only returning one possible `Exception`, and not any possible `Exception` --- .../scala-2/zio/http/UrlInterpolator.scala | 7 ++--- .../scala/zio/http/ConnectionPoolConfig.scala | 4 +-- .../src/main/scala/zio/http/Request.scala | 2 +- .../shared/src/main/scala/zio/http/URL.scala | 30 +++++++++++++------ 4 files changed, 27 insertions(+), 16 deletions(-) diff --git a/zio-http/shared/src/main/scala-2/zio/http/UrlInterpolator.scala b/zio-http/shared/src/main/scala-2/zio/http/UrlInterpolator.scala index f1a2c2864b5..c58fa33f4c4 100644 --- a/zio-http/shared/src/main/scala-2/zio/http/UrlInterpolator.scala +++ b/zio-http/shared/src/main/scala-2/zio/http/UrlInterpolator.scala @@ -32,7 +32,7 @@ private[http] object UrlInterpolatorMacro { c.prefix.tree match { case Apply(_, List(Apply(_, Literal(Constant(p: String)) :: Nil))) => val result = URL.decode(p) match { - case Left(error) => c.abort(c.enclosingPosition, s"Invalid URL: ${error.getMessage}") + case Left(error) => c.abort(c.enclosingPosition, error.getMessage) case Right(url) => val uri = url.encode q"_root_.zio.http.URL.fromURI(new _root_.java.net.URI($uri)).get" @@ -68,9 +68,8 @@ private[http] object UrlInterpolatorMacro { val exampleParts = staticParts.zipAll(injectedPartExamples, "", "").flatMap { case (a, b) => List(a, b) } val example = exampleParts.mkString URL.decode(example) match { - case Left(error) => - c.abort(c.enclosingPosition, s"Invalid URL: ${error.getMessage}") - case Right(url) => + case Left(error) => c.abort(c.enclosingPosition, error.getMessage) + case Right(_) => val parts = staticParts.map { s => Literal(Constant(s)) } .zipAll(args.map(_.tree), Literal(Constant("")), Literal(Constant(""))) diff --git a/zio-http/shared/src/main/scala/zio/http/ConnectionPoolConfig.scala b/zio-http/shared/src/main/scala/zio/http/ConnectionPoolConfig.scala index de06a6a3983..528b46d2365 100644 --- a/zio-http/shared/src/main/scala/zio/http/ConnectionPoolConfig.scala +++ b/zio-http/shared/src/main/scala/zio/http/ConnectionPoolConfig.scala @@ -47,7 +47,7 @@ object ConnectionPoolConfig { URL .decode(s) .left - .map(error => Config.Error.InvalidData(message = s"Invalid URL: ${error.getMessage}")) + .map(error => Config.Error.InvalidData(message = error.getMessage)) .flatMap { url => url.kind match { case url: URL.Location.Absolute => Right(url -> fixed) @@ -67,7 +67,7 @@ object ConnectionPoolConfig { URL .decode(s) .left - .map(error => Config.Error.InvalidData(message = s"Invalid URL: ${error.getMessage}")) + .map(error => Config.Error.InvalidData(message = error.getMessage)) .flatMap { url => url.kind match { case url: URL.Location.Absolute => Right(url -> fixed) diff --git a/zio-http/shared/src/main/scala/zio/http/Request.scala b/zio-http/shared/src/main/scala/zio/http/Request.scala index cacd4c4fe1b..baa1af48989 100644 --- a/zio-http/shared/src/main/scala/zio/http/Request.scala +++ b/zio-http/shared/src/main/scala/zio/http/Request.scala @@ -223,7 +223,7 @@ object Request { */ private def pathOrUrl(path: String): URL = if (path.startsWith("http://") || path.startsWith("https://")) { - URL.decode(path).toOption.getOrElse(URL(Path(path))) + URL.decode(path).getOrElse(URL(Path(path))) } else { URL(Path(path)) } diff --git a/zio-http/shared/src/main/scala/zio/http/URL.scala b/zio-http/shared/src/main/scala/zio/http/URL.scala index bc263788373..fb7c3210d85 100644 --- a/zio-http/shared/src/main/scala/zio/http/URL.scala +++ b/zio-http/shared/src/main/scala/zio/http/URL.scala @@ -16,7 +16,10 @@ package zio.http -import java.net.{MalformedURLException, URI} +import java.io.IOException +import java.net.URI + +import scala.util.control.NonFatal import zio.Config @@ -279,26 +282,35 @@ final case class URL( } object URL { - def empty: URL = URL(Path.empty) + val empty: URL = URL(path = Path.empty) + + /** + * Proper implementation of [[java.net.MalformedURLException]] which, unlike + * the Java version, can propagate the cause. + */ + final case class MalformedURLException private[http] (rawUrl: String, cause: Option[Throwable]) + extends IOException(cause.orNull) { + override def getMessage: String = s"""Invalid URL: "$rawUrl"""" + } - def decode(string: String): Either[Exception, URL] = { - def invalidURL(string: String) = Left(new MalformedURLException(s"""Invalid URL: "$string"""")) + def decode(rawUrl: String): Either[MalformedURLException, URL] = { + def invalidURL(e: Option[Throwable]): Either[MalformedURLException, URL] = + Left(MalformedURLException(rawUrl = rawUrl, cause = e)) try { - val uri = new URI(string) + val uri = new URI(rawUrl) val url = if (uri.isAbsolute) fromAbsoluteURI(uri) else fromRelativeURI(uri) url match { - case None => invalidURL(string) + case None => invalidURL(None) case Some(value) => Right(value) } - } catch { - case e: Exception => Left(e) + case NonFatal(e) => invalidURL(Some(e)) } } - def config: Config[URL] = Config.string.mapAttempt(decode(_).toTry.get) + def config: Config[URL] = Config.string.mapAttempt(decode(_).fold(throw _, identity)) def fromURI(uri: URI): Option[URL] = if (uri.isAbsolute) fromAbsoluteURI(uri) else fromRelativeURI(uri)