Skip to content

Commit

Permalink
[dsl] Expose PathVar class to facilitate the definition of custom Pat…
Browse files Browse the repository at this point in the history
…h extractors
  • Loading branch information
fabianhjr committed Jul 27, 2023
1 parent 956ed73 commit 2f565f0
Show file tree
Hide file tree
Showing 5 changed files with 48 additions and 16 deletions.
14 changes: 3 additions & 11 deletions docs/docs/dsl.md
Original file line number Diff line number Diff line change
Expand Up @@ -423,22 +423,14 @@ val usersService = HttpRoutes.of[IO] {
}
```

If you want to extract a variable of type `T`, you can provide a custom extractor
object which implements `def unapply(str: String): Option[T]`, similar to the way
in which `IntVar` does it.
If you want to extract a variable of type `A`, you can provide a custom extractor
which implements `cast: String => Try[A]`, similar to the way in which `IntVar` does it.

```scala mdoc:silent
import java.time.LocalDate
import scala.util.Try

object LocalDateVar {
def unapply(str: String): Option[LocalDate] = {
if (!str.isEmpty)
Try(LocalDate.parse(str)).toOption
else
None
}
}
val LocalDateVar = PathVar(str => Try(LocalDate.parse(str)))

def getTemperatureForecast(date: LocalDate): IO[Double] = IO(42.23)

Expand Down
4 changes: 4 additions & 0 deletions dsl/src/main/scala/org/http4s/dsl/Http4sDsl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import org.http4s.Method
import org.http4s.Uri
import org.http4s.dsl.impl._

import scala.util.Try

trait Http4sDsl2[F[_], G[_]] extends RequestDsl with Statuses with Responses[F, G] {
val Path: Uri.Path.type = Uri.Path
val Root: Uri.Path.Root.type = Uri.Path.Root
Expand All @@ -44,6 +46,8 @@ trait Http4sDsl2[F[_], G[_]] extends RequestDsl with Statuses with Responses[F,
*/
val : impl.->.type = impl.->

def PathVar[A](cast: String => Try[A]): impl.PathVar[A] = new impl.PathVar[A](cast)

val IntVar: impl.IntVar.type = impl.IntVar
val LongVar: impl.LongVar.type = impl.LongVar
val UUIDVar: impl.UUIDVar.type = impl.UUIDVar
Expand Down
19 changes: 14 additions & 5 deletions dsl/src/main/scala/org/http4s/dsl/impl/Path.scala
Original file line number Diff line number Diff line change
Expand Up @@ -166,12 +166,21 @@ object /: {
}
}

protected class PathVar[A](cast: String => Try[A]) {
/** Abstract extractor of a path variable:
* {{{
* enum Color:
* case Red, Green, Blue
*
* val ColorPath = new PathVar(str => Try(Color.valueOf(str)))
*
* Path("/Green") match {
* case Root / ColorPath(color) => ...
* }}}
*/
class PathVar[A](cast: String => Try[A]) {
def unapply(str: String): Option[A] =
if (!str.isEmpty)
cast(str).toOption
else
None
if (str.nonEmpty) cast(str).toOption
else None
}

/** Integer extractor of a path variable:
Expand Down
4 changes: 4 additions & 0 deletions dsl/src/main/scala/org/http4s/dsl/request.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ package org.http4s.dsl

import org.http4s.Uri

import scala.util.Try

object request extends RequestDslBinCompat {
val Path: Uri.Path.type = Uri.Path
val Root: Uri.Path.Root.type = Uri.Path.Root
Expand All @@ -28,6 +30,8 @@ object request extends RequestDslBinCompat {
val /: : impl./:.type = impl./:
val +& : impl.+&.type = impl.+&

def PathVar[A](cast: String => Try[A]): impl.PathVar[A] = new impl.PathVar[A](cast)

val IntVar: impl.IntVar.type = impl.IntVar
val LongVar: impl.LongVar.type = impl.LongVar
val UUIDVar: impl.UUIDVar.type = impl.UUIDVar
Expand Down
23 changes: 23 additions & 0 deletions dsl/src/test/scala/org/http4s/dsl/PathSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import org.http4s.syntax.AllSyntax
import org.scalacheck.Arbitrary.arbitrary
import org.scalacheck.Gen

import scala.util.Try

class PathSuite extends Http4sSuite with AllSyntax {
implicit val arbitraryPath: Gen[Path] =
arbitrary[List[String]]
Expand Down Expand Up @@ -127,6 +129,27 @@ class PathSuite extends Http4sSuite with AllSyntax {
})
}

test("Path should Abstract extractor") {
sealed trait Color
object Color {
case object Red extends Color
case object Green extends Color
case object Blue extends Color

def valueOf(str: String): Color = str match {
case "Red" => Red
case "Green" => Green
case "Blue" => Blue
}
}

val ColorVar = PathVar(str => Try(Color.valueOf(str)))
assert(path"/Green" match {
case Root / ColorVar(color) => color == Color.Green
case _ => false
})
}

test("Path should Int extractor") {
assert(path"/user/123" match {
case Root / "user" / IntVar(userId) => userId == 123
Expand Down

0 comments on commit 2f565f0

Please sign in to comment.