Skip to content

Commit

Permalink
Merge pull request #389 from softwaremill/serialize-fragments
Browse files Browse the repository at this point in the history
Add Uri methods to serialize path, query, fragment, scheme to string
  • Loading branch information
adamw authored Mar 10, 2025
2 parents 379a7ff + 468c9a0 commit 4df5d18
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 12 deletions.
51 changes: 39 additions & 12 deletions core/src/main/scala/sttp/model/Uri.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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"
}
}

Expand Down
22 changes: 22 additions & 0 deletions core/src/test/scala/sttp/model/UriTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}

0 comments on commit 4df5d18

Please sign in to comment.