diff --git a/core/src/main/scala/sttp/model/Uri.scala b/core/src/main/scala/sttp/model/Uri.scala index abce7ab..8b86401 100644 --- a/core/src/main/scala/sttp/model/Uri.scala +++ b/core/src/main/scala/sttp/model/Uri.scala @@ -279,7 +279,29 @@ case class Uri( def fragmentSegmentEncoding(encoding: Encoding): Uri = copy(fragmentSegment = fragmentSegment.map(f => f.encoding(encoding))) - override def toString: String = { + // + + /** Serializes the scheme to a string, without a trailing `:`. Might be an empty string, if no scheme is defined. */ + def schemeToString: String = scheme.map(s => encode(Rfc3986.Scheme)(s)).getOrElse("") + + /** Serializes the path to a string, encoding the segments. A leading `/` is added if the path absolute. Might be an + * empty string, if the path is relative and empty. + */ + def pathToString: String = { + val pathPrefixS = pathSegments match { + case _ if authority.isEmpty && scheme.isDefined => "" + case Uri.EmptyPath => "" + case Uri.AbsolutePath(_) => "/" + case Uri.RelativePath(_) => "" + } + val pathS = pathSegments.segments.map(_.encoded).mkString("/") + pathPrefixS + pathS + } + + /** Serializes the query to a string, encoding the segments. The leading `?` is not included. Might be an empty + * string, if there's no query. + */ + def queryToString: String = { @tailrec def encodeQuerySegments(qss: List[QuerySegment], previousWasPlain: Boolean, sb: StringBuilder): String = qss match { @@ -298,24 +320,29 @@ case class Uri( sb.append(kEnc(k)).append("=").append(vEnc(v)) encodeQuerySegments(t, previousWasPlain = false, sb) } + encodeQuerySegments(querySegments.toList, previousWasPlain = true, new StringBuilder()) + } + /** Serializes the fragment to a string, encoding the segment. The leading `#` is not included. Might be an empty + * string, if there's no fragment. + */ + def fragmentToString: String = { + // https://stackoverflow.com/questions/2053132/is-a-colon-safe-for-friendly-url-use/2053640#2053640 + fragmentSegment.fold("")(s => s.encoded) + } + + override def toString: String = { val schemeS = scheme.map(s => encode(Rfc3986.Scheme)(s) + ":").getOrElse("") val authorityS = authority.fold("")(_.toString) - val pathPrefixS = pathSegments match { - case _ if authority.isEmpty && scheme.isDefined => "" - case Uri.EmptyPath => "" - case Uri.AbsolutePath(_) => "/" - case Uri.RelativePath(_) => "" - } - val pathS = pathSegments.segments.map(_.encoded).mkString("/") - val queryPrefixS = if (querySegments.isEmpty) "" else "?" - val queryS = encodeQuerySegments(querySegments.toList, previousWasPlain = true, new StringBuilder()) + val pathS = pathToString + + val queryPrefixS = if (querySegments.isEmpty) "" else "?" + val queryS = queryToString - // https://stackoverflow.com/questions/2053132/is-a-colon-safe-for-friendly-url-use/2053640#2053640 val fragS = fragmentSegment.fold("")(s => "#" + s.encoded) - s"$schemeS$authorityS$pathPrefixS$pathS$queryPrefixS$queryS$fragS" + s"$schemeS$authorityS$pathS$queryPrefixS$queryS$fragS" } } diff --git a/core/src/test/scala/sttp/model/UriTests.scala b/core/src/test/scala/sttp/model/UriTests.scala index ca2b22d..b03d5d6 100644 --- a/core/src/test/scala/sttp/model/UriTests.scala +++ b/core/src/test/scala/sttp/model/UriTests.scala @@ -260,4 +260,26 @@ class UriTests extends AnyFunSuite with Matchers with TryValues with UriTestsExt uri"/x/y".pathSegments shouldBe Uri.AbsolutePath(List(pathSegment("x"), pathSegment("y"))) uri"${"/x/y"}".pathSegments shouldBe Uri.AbsolutePath(List(pathSegment("x"), pathSegment("y"))) } + + test("should serialize path") { + uri"http://x.com/a/b/c".pathToString shouldBe "/a/b/c" + uri"http://x.com".pathToString shouldBe "" + uri"http://x.com/".pathToString shouldBe "/" + uri"http://x.com/a%20c".pathToString shouldBe "/a%20c" + } + + test("should serialize query") { + uri"http://x.com/a/b/c".queryToString shouldBe "" + uri"http://x.com?a=b&c=d".queryToString shouldBe "a=b&c=d" + uri"http://x.com/a/b/c?p1=1%202".queryToString shouldBe "p1=1+2" + } + + test("should serialize fragment") { + uri"http://x.com/a/b/c".fragmentToString shouldBe "" + uri"http://x.com/a/b/c#d".fragmentToString shouldBe "d" + } + + test("should serialize scheme") { + uri"http://x.com/a/b/c".schemeToString shouldBe "http" + } }