Skip to content
Darren Gibson edited this page Apr 19, 2020 · 13 revisions

The RhoRoutes

(See Rho Examples Project for the most up to date examples)

The RhoRoutes[F[_]] is the primary method for creating routes. Route creation is performed in the constructor using combinators defined in the rho package object.

/// src_inlined SimplePath core/src/test/scala/ApiExamples.scala
new RhoRoutes[IO] {
  GET / "hello" |>> { () => Ok("Hello, world!") }
}
/// end_src_inlined

Combinators

The rho DSL defines helpers for creating path, query, and header rules which will match and/or extract data from the Request. All of these rules can be stored and used in the creation of more complex routes:

/// src_inlined ReusePath core/src/test/scala/ApiExamples.scala
      new RhoRoutes[IO] {
        // A path can be built up in multiple steps and the parts reused
        val pathPart1 = GET / "hello"

        pathPart1 / "world" |>> { () => Ok("Hello, world!") }
        pathPart1 / "you"   |>> { () => Ok("Hello, you!") }
      }
/// end_src_inlined

Path rules

Path rules are concerned with defining the path portion of the route. Portions of the path can be matched or captured:

/// src_inlined PathCapture core/src/test/scala/ApiExamples.scala
new RhoRoutes[IO] {
  // Use combinators to parse and capture path parameters
  GET / "helloworldnumber" / pathVar[Int] / "foo" |>> { i: Int =>
    Ok("Received $i")
  }
  // the symbol 'world just says 'capture a String' with variable name "world"
  GET / "helloworldstring" / 'world / "foo" |>> { i: String =>
    Ok("Received $i")
  }
}
/// end_src_inlined

The entire rest of the path can be captured using the * rule:

/// src_inlined CaptureTail core/src/test/scala/ApiExamples.scala
new RhoRoutes[IO] {
  // You can capture the entire rest of the tail using *
  GET / "hello" / * |>> { r: List[String] =>
    Ok(s"Got the rest: ${r.mkString}")
  }
}
/// end_src_inlined

Query rules

Query rules are captured in essentially the same manner as path rules:

/// src_inlined QueryCapture core/src/test/scala/ApiExamples.scala
new RhoRoutes[IO] {
  // Query parameters can be captured in a similar manner as path fragments
  GET / "hello" +? param[Int]("fav") |>> { i: Int =>
    Ok(s"Query 'fav' had Int value $i")
  }
}
/// end_src_inlined

Of course you can use query capture rules with path capture rules:

/// src_inlined MultiCapture core/src/test/scala/ApiExamples.scala
new RhoRoutes[IO] {
  // A Path can be made all at once
  POST / pathVar[Int] +? param[Int]("fav") |>> { (i1: Int, i2: Int) =>
    Ok(s"Sum of the number is ${i1 + i2}")
  }
}
/// end_src_inlined

Header rules

Headers work essentially the same as path and query rules:

/// src_inlined HeaderCapture core/src/test/scala/ApiExamples.scala
new RhoRoutes[IO] {
  GET / "hello" >>> capture(ETag) |>> { tag: ETag =>
    Ok(s"Thanks for the tag: $tag")
  }
}
/// end_src_inlined

Result types

Status codes are not necessarily required: rho will accept anything with an EntityEncoder:

/// src_inlined ResultTypes core/src/test/scala/ApiExamples.scala
      new RhoRoutes[IO] {
        private val counter = new AtomicInteger(0)
        private def getCount(): String = counter.incrementAndGet().toString
        // Don't want status codes? Anything with an `EntityEncoder` will work.
        GET / "nostatus" |>> { () => "No status!" }
        GET / "taskNoStatus" |>> { () => Task(getCount())
        }

        /* Results need not be functions: they can be anything that has
           an `EntityEncoder` instance in scope */
        GET / "nostatus2" |>> "This is a constant result!"
        GET / "taskNoStatus2" |>> Task(getCount())

        /* We can use a standard http4s.Response, but we don't get any metadata
           with it. Useful for things like Websocket support. */
        GET / "websockets" |>> { () =>
          val exchange: stream.Exchange[WebSocketFrame,WebSocketFrame] = ???
          WS(exchange)
        }
      }
/// end_src_inlined

Status Codes

Rho stores as much metadata as it can, including status codes and the response types that accompany them which is very useful for middleware that can take advantage of that information (such as Swagger generation). In order to capture that data use the status code helpers:

/// src_inlined StatusCodes core/src/test/scala/ApiExamples.scala
new RhoRoutes[IO] {
  GET / "twoResults" |>> { () =>
    if (true) Ok("bytes result".getBytes())
    else NotFound("Boo... Not found...")
  }
}
/// end_src_inlined

Request Bodies

For requests that contain a body rho can extract and decode it using the ^ operator with http4s EntityDecoder[A]'s:

/// src_inlined Decoders core/src/test/scala/ApiExamples.scala
new RhoRoutes[IO] {
  // Using decoders you can parse the body as well
  POST / "postSomething" ^ UrlForm.entityDecoder |>> { m: UrlForm =>
    Ok(s"You posted these things: $m")
  }
}
/// end_src_inlined