sake92 / stone   0.5.0

Apache License 2.0 GitHub

URL (de)construct. Withers.

Scala versions: 2.13
Scala.js versions: 1.x

Stone Maven Central Build Status

Handy Scala macros for everyday use: Route, Wither.

Scala 2.13 only!
ScalaJS 1 is supported.

@Route

Generates apply/unapply methods for extracting/constructing URLs. Here's why:

  • type safe URLs/routes
  • unlike Play SIRD and others, it can also construct a URL

In Play, Angular and other frameworks you'd write something like this:
/users/:id/:name ? minAge=:minAge & qs=:qs...

With @Route macro you write this:

@Route
class UsersRoute( /* first list contains path params */
    p1: "users",      // fixed
    val id: Long,     // variable
    val name: String
)(                /* second list contains query params (those after ?) */
    val minAge: Int,      // mandatory
    val opt: Option[Int], // optional
    val qs: Set[String]   // multi-valued
)

// construct a URL, type-safely
val route = UsersRoute(1, "Sake")(123, Some(456), Set("q1"))
val redirectUrl = route.urlData.url // /users/1/Sake?minAge=18&opt=456&qs=q1

// deconstruct a string URL to type-safe data
"users/1/Sake?minAge=123&qs=q1&qs=q2&opt=456" match {
  case UsersRoute(id, name, minAge, opt, qs) =>
    println(s"$id, $name, $minAge, $opt, $qs") // 1, Sake, 123, Some(456), Set(q1, q2)
  case _ => println("404 Not Found")
}

Match path segment regex

Regex is supported, just put it inside angle brackets:

@Route class RegexRoute(p1: "users", val name: "<[a-z]+>")

This would match a string /users/tom, but not /users/Tom.

Match multiple path segments

You can match on multi-segment path with a *:

@Route class StarRoute(p1: "files", val path: "*")

This would match a string /files/images/abc.jpg etc.
Basically, anything starting with /files...


@Wither

Generates with* methods. Here's why:

  • more readable than named args
  • autocomplete is nicer
  • additional withers for Option, List etc

If you have this:

@Wither
class MyClass(
  simple: Int,
  opt: Option[Int],
  list: List[Int]
)

you get to write:

val data = new ExampleData(1, Some(10), List(100))

data.withSimple(2)            // MyClass(2, Some(10), List(100))

data.withOpt(Some(11))        // MyClass(2, Some(11), List(100))
data.withOpt(12)              // MyClass(2, Some(12), List(100))

data.withList(List(101, 102)) // MyClass(7, None, List(101,102))
data.withList(103, 104)       // MyClass(7, None, List(103,104))

data.withSimple(2).withOpt(12).withList(103, 104) // MyClass(2, Some(12), List(103,104))