Skip to content

Commit 468c9a0

Browse files
committed
Add Uri methods to serialize path, query, fragment, scheme to string
1 parent 379a7ff commit 468c9a0

File tree

2 files changed

+61
-12
lines changed

2 files changed

+61
-12
lines changed

core/src/main/scala/sttp/model/Uri.scala

+39-12
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,29 @@ case class Uri(
279279
def fragmentSegmentEncoding(encoding: Encoding): Uri =
280280
copy(fragmentSegment = fragmentSegment.map(f => f.encoding(encoding)))
281281

282-
override def toString: String = {
282+
//
283+
284+
/** Serializes the scheme to a string, without a trailing `:`. Might be an empty string, if no scheme is defined. */
285+
def schemeToString: String = scheme.map(s => encode(Rfc3986.Scheme)(s)).getOrElse("")
286+
287+
/** Serializes the path to a string, encoding the segments. A leading `/` is added if the path absolute. Might be an
288+
* empty string, if the path is relative and empty.
289+
*/
290+
def pathToString: String = {
291+
val pathPrefixS = pathSegments match {
292+
case _ if authority.isEmpty && scheme.isDefined => ""
293+
case Uri.EmptyPath => ""
294+
case Uri.AbsolutePath(_) => "/"
295+
case Uri.RelativePath(_) => ""
296+
}
297+
val pathS = pathSegments.segments.map(_.encoded).mkString("/")
298+
pathPrefixS + pathS
299+
}
300+
301+
/** Serializes the query to a string, encoding the segments. The leading `?` is not included. Might be an empty
302+
* string, if there's no query.
303+
*/
304+
def queryToString: String = {
283305
@tailrec
284306
def encodeQuerySegments(qss: List[QuerySegment], previousWasPlain: Boolean, sb: StringBuilder): String =
285307
qss match {
@@ -298,24 +320,29 @@ case class Uri(
298320
sb.append(kEnc(k)).append("=").append(vEnc(v))
299321
encodeQuerySegments(t, previousWasPlain = false, sb)
300322
}
323+
encodeQuerySegments(querySegments.toList, previousWasPlain = true, new StringBuilder())
324+
}
301325

326+
/** Serializes the fragment to a string, encoding the segment. The leading `#` is not included. Might be an empty
327+
* string, if there's no fragment.
328+
*/
329+
def fragmentToString: String = {
330+
// https://stackoverflow.com/questions/2053132/is-a-colon-safe-for-friendly-url-use/2053640#2053640
331+
fragmentSegment.fold("")(s => s.encoded)
332+
}
333+
334+
override def toString: String = {
302335
val schemeS = scheme.map(s => encode(Rfc3986.Scheme)(s) + ":").getOrElse("")
303336
val authorityS = authority.fold("")(_.toString)
304-
val pathPrefixS = pathSegments match {
305-
case _ if authority.isEmpty && scheme.isDefined => ""
306-
case Uri.EmptyPath => ""
307-
case Uri.AbsolutePath(_) => "/"
308-
case Uri.RelativePath(_) => ""
309-
}
310-
val pathS = pathSegments.segments.map(_.encoded).mkString("/")
311-
val queryPrefixS = if (querySegments.isEmpty) "" else "?"
312337

313-
val queryS = encodeQuerySegments(querySegments.toList, previousWasPlain = true, new StringBuilder())
338+
val pathS = pathToString
339+
340+
val queryPrefixS = if (querySegments.isEmpty) "" else "?"
341+
val queryS = queryToString
314342

315-
// https://stackoverflow.com/questions/2053132/is-a-colon-safe-for-friendly-url-use/2053640#2053640
316343
val fragS = fragmentSegment.fold("")(s => "#" + s.encoded)
317344

318-
s"$schemeS$authorityS$pathPrefixS$pathS$queryPrefixS$queryS$fragS"
345+
s"$schemeS$authorityS$pathS$queryPrefixS$queryS$fragS"
319346
}
320347
}
321348

core/src/test/scala/sttp/model/UriTests.scala

+22
Original file line numberDiff line numberDiff line change
@@ -260,4 +260,26 @@ class UriTests extends AnyFunSuite with Matchers with TryValues with UriTestsExt
260260
uri"/x/y".pathSegments shouldBe Uri.AbsolutePath(List(pathSegment("x"), pathSegment("y")))
261261
uri"${"/x/y"}".pathSegments shouldBe Uri.AbsolutePath(List(pathSegment("x"), pathSegment("y")))
262262
}
263+
264+
test("should serialize path") {
265+
uri"http://x.com/a/b/c".pathToString shouldBe "/a/b/c"
266+
uri"http://x.com".pathToString shouldBe ""
267+
uri"http://x.com/".pathToString shouldBe "/"
268+
uri"http://x.com/a%20c".pathToString shouldBe "/a%20c"
269+
}
270+
271+
test("should serialize query") {
272+
uri"http://x.com/a/b/c".queryToString shouldBe ""
273+
uri"http://x.com?a=b&c=d".queryToString shouldBe "a=b&c=d"
274+
uri"http://x.com/a/b/c?p1=1%202".queryToString shouldBe "p1=1+2"
275+
}
276+
277+
test("should serialize fragment") {
278+
uri"http://x.com/a/b/c".fragmentToString shouldBe ""
279+
uri"http://x.com/a/b/c#d".fragmentToString shouldBe "d"
280+
}
281+
282+
test("should serialize scheme") {
283+
uri"http://x.com/a/b/c".schemeToString shouldBe "http"
284+
}
263285
}

0 commit comments

Comments
 (0)