Skip to content

Commit

Permalink
Finch in Action
Browse files Browse the repository at this point in the history
  • Loading branch information
vkostyukov committed Mar 16, 2015
1 parent 49edd59 commit ee8a1cd
Show file tree
Hide file tree
Showing 10 changed files with 246 additions and 110 deletions.
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ lazy val demo = project
lazy val playground = project
.settings(moduleName := "finch-playground")
.settings(allSettings: _*)
.dependsOn(core)
.dependsOn(core, jackson)
.disablePlugins(CoverallsPlugin)

lazy val jawn = project
Expand Down
51 changes: 51 additions & 0 deletions core/src/main/scala/io/finch/micro/package.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package io.finch

import com.twitter.finagle.Service
import com.twitter.util.Future

import io.finch.route.{Endpoint => _, _}
import io.finch.response._
import io.finch.request._

/**
* An experimental package that enables `micro`-services support in Finch.
*/
package object micro {

/**
* `RequestReader` is a composable microservice or just `Micro`.
*/
type Micro[A] = RequestReader[A]

/**
* A companion object for `Micro`.
*/
val Micro = RequestReader

/**
*
*/
type Endpoint = Router[Micro[HttpResponse]]

implicit class MicroRouterOps[A](r: Router[Micro[A]]) {
def |[B](that: Router[Micro[B]])(implicit eA: EncodeResponse[A], eB: EncodeResponse[B]): Endpoint =
r.map(_.map(Ok(_))) orElse that.map(_.map(Ok(_)))
}

implicit def microToHttpMicro[A](m: Micro[A])(
implicit e: EncodeResponse[A]
): Micro[HttpResponse] = m.map(Ok(_))

implicit def microRouterToEndpoint[M](r: Router[M])(
implicit ev: M => Micro[HttpResponse]
): Endpoint = r.map(ev)

implicit def endpointToFinagleService[M](r: Router[M])(
implicit ev: M => Micro[HttpResponse]
): Service[HttpRequest, HttpResponse] = new Service[HttpRequest, HttpResponse] {
def apply(req: HttpRequest): Future[HttpResponse] = r.map(ev)(requestToRoute(req)) match {
case Some((Nil, micro)) => micro(req)
case _ => RouteNotFound(s"${req.method.toString.toUpperCase} ${req.path}").toFutureException
}
}
}
6 changes: 3 additions & 3 deletions core/src/main/scala/io/finch/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@
package io

import com.twitter.finagle.httpx.path.Path

import scala.language.implicitConversions
import io.finch.response.{Ok, EncodeResponse}

import com.twitter.finagle.httpx
import com.twitter.util.Future
import com.twitter.finagle.{Filter, Service}
import com.twitter.finagle.Service
import com.twitter.finagle.Filter

/**
* This is a root package of the Finch library, which provides an immutable layer of functions and types atop of Finagle
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package io.finch.request

import com.twitter.util.{Future, Try}

import scala.reflect.ClassTag

/**
* Trait with low-priority implicits to avoid conflicts that would arise from adding implicits that would work with
* any type in the same scope as implicits for concrete types.
*
* Implicits defined in super-types have lower priority than those defined in a sub-type. Therefore we define low-
* priority implicits here and mix this trait into the package object.
*/
trait LowPriorityRequestReaderImplicits {

/**
* Creates a [[io.finch.request.DecodeMagnet DecodeMagnet]] from
* [[io.finch.request.DecodeAnyRequest DecodeAnyRequest]].
*/
implicit def magnetFromAnyDecode[A](implicit d: DecodeAnyRequest, tag: ClassTag[A]): DecodeMagnet[A] =
new DecodeMagnet[A] {
def apply(): DecodeRequest[A] = new DecodeRequest[A] {
def apply(req: String): Try[A] = d(req)(tag)
}
}

/**
* Adds a `~>` and `~~>` compositors to `RequestReader` to compose it with function of one argument.
*/
implicit class RrArrow1[A](rr: RequestReader[A]) {
def ~~>[B](fn: A => Future[B]): RequestReader[B] =
rr.embedFlatMap(fn)

def ~>[B](fn: A => B): RequestReader[B] =
rr.map(fn)
}
}
103 changes: 78 additions & 25 deletions core/src/main/scala/io/finch/request/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -35,30 +35,6 @@ import org.jboss.netty.handler.codec.http.multipart.{HttpPostRequestDecoder, Att
import scala.collection.JavaConverters._
import scala.reflect.ClassTag

package request {

/**
* Trait with low-priority implicits to avoid conflicts that would arise from adding implicits that would work with
* any type in the same scope as implicits for concrete types.
*
* Implicits defined in super-types have lower priority than those defined in a sub-type. Therefore we define low-
* priority implicits here and mix this trait into the package object.
*/
trait LowPriorityImplicits {

/**
* Creates a [[io.finch.request.DecodeMagnet DecodeMagnet]] from
* [[io.finch.request.DecodeAnyRequest DecodeAnyRequest]].
*/
implicit def magnetFromAnyDecode[A](implicit d: DecodeAnyRequest, tag: ClassTag[A]): DecodeMagnet[A] =
new DecodeMagnet[A] {
def apply(): DecodeRequest[A] = new DecodeRequest[A] {
def apply(req: String): Try[A] = d(req)(tag)
}
}
}
}

/**
* This package introduces types and functions that enable _request processing_ in Finch. The [[io.finch.request]]
* primitives allow both to _read_ the various request items (''query string param'', ''header'' and ''cookie'') using
Expand Down Expand Up @@ -93,7 +69,7 @@ package request {
* }
* }}}
*/
package object request extends LowPriorityImplicits {
package object request extends LowPriorityRequestReaderImplicits {

/**
* A type alias for a [[org.jboss.netty.handler.codec.http.multipart.FileUpload]]
Expand Down Expand Up @@ -247,6 +223,83 @@ package object request extends LowPriorityImplicits {
case None => true
}

/**
* Adds a `~>` and `~~>` compositors to `RequestReader` to compose it with function of two arguments.
*/
implicit class RrArrow2[A, B](val rr: RequestReader[A ~ B]) extends AnyVal {
def ~~>[C](fn: (A, B) => Future[C]): RequestReader[C] =
rr.embedFlatMap { case (a ~ b) => fn(a, b) }

def ~>[C](fn: (A, B) => C): RequestReader[C] =
rr.map { case (a ~ b) => fn(a, b) }
}

/**
* Adds a `~>` and `~~>` compositors to `RequestReader` to compose it with function of three arguments.
*/
implicit class RrArrow3[A, B, C](val rr: RequestReader[A ~ B ~ C]) extends AnyVal {
def ~~>[D](fn: (A, B, C) => Future[D]): RequestReader[D] =
rr.embedFlatMap { case (a ~ b ~ c) => fn(a, b, c) }

def ~>[D](fn: (A, B, C) => D): RequestReader[D] =
rr.map { case (a ~ b ~ c) => fn(a, b, c) }
}

/**
* Adds a `~>` and `~~>` compositors to `RequestReader` to compose it with function of four arguments.
*/
implicit class RrArrow4[A, B, C, D](val rr: RequestReader[A ~ B ~ C ~ D]) extends AnyVal {
def ~~>[E](fn: (A, B, C, D) => Future[E]): RequestReader[E] =
rr.embedFlatMap { case (a ~ b ~ c ~ d) => fn(a, b, c, d) }

def ~>[E](fn: (A, B, C, D) => E): RequestReader[E] =
rr.map { case (a ~ b ~ c ~ d) => fn(a, b, c, d) }
}

/**
* Adds a `~>` and `~~>` compositors to `RequestReader` to compose it with function of five arguments.
*/
implicit class RrArrow5[A, B, C, D, E](val rr: RequestReader[A ~ B ~ C ~ D ~ E]) extends AnyVal {
def ~~>[F](fn: (A, B, C, D, E) => Future[F]): RequestReader[F] =
rr.embedFlatMap { case (a ~ b ~ c ~ d ~ e) => fn(a, b, c, d, e) }

def ~>[F](fn: (A, B, C, D, E) => F): RequestReader[F] =
rr.map { case (a ~ b ~ c ~ d ~ e) => fn(a, b, c, d, e) }
}

/**
* Adds a `~>` and `~~>` compositors to `RequestReader` to compose it with function of six arguments.
*/
implicit class RrArrow6[A, B, C, D, E, F](val rr: RequestReader[A ~ B ~ C ~ D ~ E ~ F]) extends AnyVal {
def ~~>[G](fn: (A, B, C, D, E, F) => Future[G]): RequestReader[G] =
rr.embedFlatMap { case (a ~ b ~ c ~ d ~ e ~ f) => fn(a, b, c, d, e, f) }

def ~>[G](fn: (A, B, C, D, E, F) => G): RequestReader[G] =
rr.map { case (a ~ b ~ c ~ d ~ e ~ f) => fn(a, b, c, d, e, f) }
}

/**
* Adds a `~>` and `~~>` compositors to `RequestReader` to compose it with function of seven arguments.
*/
implicit class RrArrow7[A, B, C, D, E, F, G](val rr: RequestReader[A ~ B ~ C ~ D ~ E ~ F ~ G]) extends AnyVal {
def ~~>[H](fn: (A, B, C, D, E, F, G) => Future[H]): RequestReader[H] =
rr.embedFlatMap { case (a ~ b ~ c ~ d ~ e ~ f ~ g) => fn(a, b, c, d, e, f, g) }

def ~>[H](fn: (A, B, C, D, E, F, G) => H): RequestReader[H] =
rr.map { case (a ~ b ~ c ~ d ~ e ~ f ~ g) => fn(a, b, c, d, e, f, g) }
}

/**
* Adds a `~>` and `~~>` compositors to `RequestReader` to compose it with function of eight arguments.
*/
implicit class RrArrow8[A, B, C, D, E, F, G, H](val rr: RequestReader[A ~ B ~ C ~ D ~ E ~ F ~ G ~ H]) extends AnyVal {
def ~~>[I](fn: (A, B, C, D, E, F, G, H) => Future[I]): RequestReader[I] =
rr.embedFlatMap { case (a ~ b ~ c ~ d ~ e ~ f ~ g ~ h) => fn(a, b, c, d, e, f, g, h) }

def ~>[I](fn: (A, B, C, D, E, F, G, H) => I): RequestReader[I] =
rr.map { case (a ~ b ~ c ~ d ~ e ~ f ~ g ~ h) => fn(a, b, c, d, e, f, g, h) }
}

// Helper functions.
private[request] def requestParam(param: String)(req: HttpRequest): Option[String] =
req.params.get(param) orElse {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package io.finch.route

trait LowPriorityRouterImplicits {

/**
* Add `/>` compositors to `RouterN` to compose it with function of one argument.
*/
implicit class RArrow1[A](r: RouterN[A]) {
def />[B](fn: A => B): RouterN[B] = r.map(fn)
def |[B >: A](that: RouterN[B]): RouterN[B] = r orElse that
}
}
13 changes: 0 additions & 13 deletions core/src/main/scala/io/finch/route/Router.scala
Original file line number Diff line number Diff line change
Expand Up @@ -119,19 +119,6 @@ trait RouterN[+A] { self =>
def /(that: Router0): RouterN[A] =
this andThen that

/**
* Maps this router to the given function `A => B`.
*/
def />[B](fn: A => B): RouterN[B] =
this map fn

/**
* Sequentially composes this router with the given `that` router. The resulting router will succeed if either this or
* `that` routers are succeed.
*/
def |[B >: A](that: RouterN[B]): RouterN[B] =
this orElse that

// A workaround for https://issues.scala-lang.org/browse/SI-1336
def withFilter(p: A => Boolean): RouterN[A] = self
}
Expand Down
43 changes: 33 additions & 10 deletions core/src/main/scala/io/finch/route/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ import com.twitter.util.Future
* )
* }}}
*/
package object route {
package object route extends LowPriorityRouterImplicits {

object tokens {
//
Expand Down Expand Up @@ -89,23 +89,46 @@ package object route {
*/
case class RouteNotFound(r: String) extends Exception(s"Route not found: $r")

/**
* Converts `Req` to `Route`.
*/
private[finch] def requestToRoute[Req](req: Req)(implicit ev: Req => HttpRequest): Route =
(MethodToken(req.method): RouteToken) :: (req.path.split("/").toList.drop(1) map PathToken)

/**
* Implicitly converts the given `Router[Service[_, _]]` into a service.
*/
implicit def endpointToService[Req, Rep](
r: RouterN[Service[Req, Rep]]
)(implicit ev: Req => HttpRequest): Service[Req, Rep] = new Service[Req, Rep] {
def apply(req: Req): Future[Rep] = r(requestToRoute(req)) match {
case Some((Nil, service)) => service(req)
case _ => RouteNotFound(s"${req.method.toString.toUpperCase} ${req.path}").toFutureException
}
}

private def requestToRoute(req: Req)(implicit ev: Req => HttpRequest): Route =
(MethodToken(req.method): RouteToken) :: (req.path.split("/").toList.drop(1) map PathToken)
/**
* Add `/>` compositor to `RouterN` to compose it with function of two argument.
*/
implicit class RArrow2[A, B](val r: RouterN[A / B]) extends AnyVal {
def />[C](fn: (A, B) => C): RouterN[C] =
r.map { case a / b => fn(a, b) }
}

def apply(req: Req): Future[Rep] = {
val path = requestToRoute(req)
r(path) match {
case Some((Nil, service)) => service(req)
case _ => RouteNotFound(s"${req.method.toString.toUpperCase} ${req.path}").toFutureException
}
}
/**
* Add `/>` compositor to `RouterN` to compose it with function of three argument.
*/
implicit class RArrow3[A, B, C](val r: RouterN[A / B / C]) extends AnyVal {
def />[D](fn: (A, B, C) => D): RouterN[D] =
r.map { case a / b / c => fn(a, b, c) }
}

/**
* Add `/>` compositor to `RouterN` to compose it with function of four argument.
*/
implicit class RArrow4[A, B, C, D](val r: RouterN[A / B / C / D]) extends AnyVal {
def />[E](fn: (A, B, C, D) => E): RouterN[E] =
r.map { case a / b / c / d => fn(a, b, c, d) }
}

implicit def intToMatcher(i: Int): Router0 = new Matcher(i.toString)
Expand Down
2 changes: 1 addition & 1 deletion core/src/test/scala/io/finch/route/RouterSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ class RouterSpec extends FlatSpec with Matchers {

it should "be composable as an endpoint" in {
val r1 = Get / "a" / int /> { _ + 10 }
val r2 = Get / "b" / int / int /> { case a / b => a + b }
val r2 = Get / "b" / int / int /> { _ + _ }
val r3 = r1 | r2

r3(route) shouldBe Some((route.drop(3), 11))
Expand Down
Loading

0 comments on commit ee8a1cd

Please sign in to comment.