Skip to content

Commit

Permalink
Multi-value segments for PathCodecs (#2959)
Browse files Browse the repository at this point in the history
* First pass on segment codecs

* Allocation free path checking (#2679)

* Improve API to generate combined segments and enforce structure (#2679)

---------

Co-authored-by: Kyri Petrou <[email protected]>
  • Loading branch information
987Nabil and kyri-petrou committed Jul 15, 2024
1 parent 25bd6ff commit df1523e
Show file tree
Hide file tree
Showing 9 changed files with 799 additions and 126 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ private[cli] object HttpOptions {
private[cli] def optionsFromSegment(segment: SegmentCodec[_]): Options[String] = {
def fromSegment[A](segment: SegmentCodec[A]): Options[String] =
segment match {
case SegmentCodec.UUID(name) =>
case SegmentCodec.UUID(name) =>
Options
.text(name)
.mapOrFail(str =>
Expand All @@ -324,13 +324,14 @@ private[cli] object HttpOptions {
},
)
.map(_.toString)
case SegmentCodec.Text(name) => Options.text(name)
case SegmentCodec.IntSeg(name) => Options.integer(name).map(_.toInt).map(_.toString)
case SegmentCodec.LongSeg(name) => Options.integer(name).map(_.toInt).map(_.toString)
case SegmentCodec.BoolSeg(name) => Options.boolean(name).map(_.toString)
case SegmentCodec.Literal(value) => Options.Empty.map(_ => value)
case SegmentCodec.Trailing => Options.none.map(_.toString)
case SegmentCodec.Empty => Options.none.map(_.toString)
case SegmentCodec.Text(name) => Options.text(name)
case SegmentCodec.IntSeg(name) => Options.integer(name).map(_.toInt).map(_.toString)
case SegmentCodec.LongSeg(name) => Options.integer(name).map(_.toInt).map(_.toString)
case SegmentCodec.BoolSeg(name) => Options.boolean(name).map(_.toString)
case SegmentCodec.Literal(value) => Options.Empty.map(_ => value)
case SegmentCodec.Trailing => Options.none.map(_.toString)
case SegmentCodec.Empty => Options.none.map(_.toString)
case SegmentCodec.Combined(_, _, _) => throw new IllegalArgumentException("Combined segment not supported")
}

fromSegment(segment)
Expand Down
18 changes: 10 additions & 8 deletions zio-http-cli/src/test/scala/zio/http/endpoint/cli/CommandGen.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,16 @@ object CommandGen {
def getSegment(segment: SegmentCodec[_]): (String, String) = {
def fromSegment[A](segment: SegmentCodec[A]): (String, String) =
segment match {
case SegmentCodec.UUID(name) => (name, "text")
case SegmentCodec.Text(name) => (name, "text")
case SegmentCodec.IntSeg(name) => (name, "integer")
case SegmentCodec.LongSeg(name) => (name, "integer")
case SegmentCodec.BoolSeg(name) => (name, "boolean")
case SegmentCodec.Literal(_) => ("", "")
case SegmentCodec.Trailing => ("", "")
case SegmentCodec.Empty => ("", "")
case SegmentCodec.UUID(name) => (name, "text")
case SegmentCodec.Text(name) => (name, "text")
case SegmentCodec.IntSeg(name) => (name, "integer")
case SegmentCodec.LongSeg(name) => (name, "integer")
case SegmentCodec.BoolSeg(name) => (name, "boolean")
case SegmentCodec.Literal(_) => ("", "")
case SegmentCodec.Trailing => ("", "")
case SegmentCodec.Empty => ("", "")
case SegmentCodec.Combined(left, right, combiner) =>
???
}

fromSegment(segment)
Expand Down
69 changes: 69 additions & 0 deletions zio-http/jvm/src/test/scala/zio/http/codec/PathCodecSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import zio._
import zio.test._

import zio.http._
import zio.http.codec.PathCodec.Segment
import zio.http.codec._

object PathCodecSpec extends ZIOHttpSpec {
Expand Down Expand Up @@ -115,6 +116,67 @@ object PathCodecSpec extends ZIOHttpSpec {
)
},
),
suite("decoding with sub-segment codecs")(
test("int") {
val codec = PathCodec.empty /
string("foo") /
"instances" /
int("a") ~ "_" ~ int("b") /
"bar" /
int("baz")

assertTrue(codec.decode(Path("/abc/instances/123_13/bar/42")) == Right(("abc", 123, 13, 42)))
},
test("uuid") {
val codec = PathCodec.empty /
string("foo") /
"foo" /
uuid("a") ~ "__" ~ int("b") /
"bar" /
int("baz")

val id = UUID.randomUUID()
val p = s"/abc/foo/${id}__13/bar/42"
assertTrue(codec.decode(Path(p)) == Right(("abc", id, 13, 42)))
},
test("string before literal") {
val codec = PathCodec.empty /
string("foo") /
"foo" /
string("a") ~ "__" ~ int("b") /
"bar" /
int("baz")
assertTrue(codec.decode(Path("/abc/foo/cba__13/bar/42")) == Right(("abc", "cba", 13, 42)))
},
test("string before int") {
val codec = PathCodec.empty /
string("foo") /
"foo" /
string("a") ~ int("b") /
"bar" /
int("baz")
assertTrue(codec.decode(Path("/abc/foo/cba13/bar/42")) == Right(("abc", "cba", 13, 42)))
},
test("string before long") {
val codec = PathCodec.empty /
string("foo") /
"foo" /
string("a") ~ long("b") /
"bar" /
int("baz")
assertTrue(codec.decode(Path("/abc/foo/cba133333333333/bar/42")) == Right(("abc", "cba", 133333333333L, 42)))
},
test("trailing literal") {
val codec = PathCodec.empty /
string("foo") /
"instances" /
int("a") ~ "what" /
"bar" /
int("baz")

assertTrue(codec.decode(Path("/abc/instances/123what/bar/42")) == Right(("abc", 123, 42)))
},
),
suite("representation")(
test("empty") {
val codec = PathCodec.empty
Expand Down Expand Up @@ -149,6 +211,13 @@ object PathCodecSpec extends ZIOHttpSpec {

assertTrue(codec.render == "/users/{user-id}/posts/{post-id}")
},
test("/users/{first-name}_{last-name}") {
val codec =
PathCodec.empty / PathCodec.literal("users") /
string("first-name") ~ "_" ~ string("last-name")

assertTrue(codec.render == "/users/{first-name}_{last-name}")
},
test("transformed") {
val codec =
PathCodec.path("/users") /
Expand Down
Loading

0 comments on commit df1523e

Please sign in to comment.