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 24, 2015
1 parent 49edd59 commit e6793fb
Show file tree
Hide file tree
Showing 13 changed files with 494 additions and 357 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
64 changes: 64 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,64 @@
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 {

/**
* An alias for polymorphic [[PRequestReader]].
*/
type PMicro[R, A] = PRequestReader[R, A]

/**
* A [[PMicro]] with request type fixed to [[HttpRequest]].
*/
type Micro[A] = PMicro[HttpRequest, A]

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

/**
* A [[Router]] that fetches a [[PMicro]] is called an endpoint.
*/
type PEndpoint[R] = Router[PMicro[R, HttpResponse]]

/**
* A [[PEndpoint]] with request type fixed to [[HttpRequest]].
*/
type Endpoint = PEndpoint[HttpRequest]

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

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

implicit def microRouterToEndpoint[R, M](r: Router[M])(
implicit ev: M => PMicro[R, HttpResponse]
): PEndpoint[R] = r.map(ev)

implicit def endpointToFinagleService[M, R](r: Router[M])(
implicit evM: M => PMicro[R, HttpResponse], evR: R %> HttpRequest
): Service[R, HttpResponse] = new Service[R, HttpResponse] {
def apply(req: R): Future[HttpResponse] = {
val httpReq = evR(req)
r.map(evM)(requestToRoute(httpReq)) match {
case Some((Nil, micro)) => micro(req)
case _ => RouteNotFound(s"${httpReq.method.toString.toUpperCase} ${httpReq.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[R, A](rr: PRequestReader[R, A]) {
def ~~>[B](fn: A => Future[B]): PRequestReader[R, B] =
rr.embedFlatMap(fn)

def ~>[B](fn: A => B): PRequestReader[R, B] =
rr.map(fn)
}
}
72 changes: 31 additions & 41 deletions core/src/main/scala/io/finch/request/RequestReader.scala
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ import io.finch._
import io.finch.request.items._

/**
* A request reader (a reader monad) that reads a [[com.twitter.util.Future Future]] of `A` from the HTTP request.
* A polymorphic request reader (a reader monad) that reads a [[Future]] of `A` from the request of type `R`.
*/
trait RequestReader[A] { self =>
trait PRequestReader[R, A] { self =>

/**
* A [[io.finch.request.items.RequestItem RequestItem]] read by this request reader.
Expand All @@ -43,50 +43,48 @@ trait RequestReader[A] { self =>
/**
* Reads the data from given request `req`.
*
* @tparam Req the request type
* @param req the request to read
*/
def apply[Req](req: Req)(implicit ev: Req => HttpRequest): Future[A]
def apply(req: R): Future[A]

/**
* Flat-maps this request reader to the given function `A => RequestReader[B]`.
*/
def flatMap[B](fn: A => RequestReader[B]): RequestReader[B] = new RequestReader[B] {
def flatMap[B](fn: A => PRequestReader[R, B]): PRequestReader[R, B] = new PRequestReader[R, B] {
val item = MultipleItems
def apply[Req](req: Req)(implicit ev: Req => HttpRequest): Future[B] = self(req) flatMap { fn(_)(req) }
def apply(req: R): Future[B] = self(req) flatMap { fn(_)(req) }
}

/**
* Maps this request reader to the given function `A => B`.
*/
def map[B](fn: A => B): RequestReader[B] = new RequestReader[B] {
def map[B](fn: A => B): PRequestReader[R, B] = new PRequestReader[R, B] {
val item = self.item
def apply[Req](req: Req)(implicit ev: Req => HttpRequest): Future[B] = self(req) map fn
def apply(req: R): Future[B] = self(req) map fn
}

/**
* Flat-maps this request reader to the given function `A => Future[B]`.
*/
def embedFlatMap[B](fn: A => Future[B]): RequestReader[B] = new RequestReader[B] {
def embedFlatMap[B](fn: A => Future[B]): PRequestReader[R, B] = new PRequestReader[R, B] {
val item = self.item
def apply[Req](req: Req)(implicit ev: Req => HttpRequest): Future[B] = self(req) flatMap fn
def apply(req: R): Future[B] = self(req) flatMap fn
}

/**
* Composes this request reader with the given `that` request reader.
*/
def ~[B](that: RequestReader[B]): RequestReader[A ~ B] = new RequestReader[A ~ B] {
def ~[S, B](magnet: ApplicativeMagnet[R, A, S, B]): PRequestReader[S, A ~ B] = new PRequestReader[S, A ~ B] {
val item = MultipleItems
def apply[Req] (req: Req)(implicit ev: Req => HttpRequest): Future[A ~ B] =
Future.join(self(req)(ev).liftToTry, that(req)(ev).liftToTry) flatMap {
case (Return(a), Return(b)) => new ~(a, b).toFuture
case (Throw(a), Throw(b)) => collectExceptions(a, b).toFutureException
case (Throw(e), _) => e.toFutureException
case (_, Throw(e)) => e.toFutureException
}
def apply(req: S): Future[A ~ B] = magnet(req, self).flatMap {
case (Return(a), Return(b)) => new ~(a, b).toFuture
case (Throw(a), Throw(b)) => collectExceptions(a, b).toFutureException
case (Throw(e), _) => e.toFutureException
case (_, Throw(e)) => e.toFutureException
}

def collectExceptions (a: Throwable, b: Throwable): RequestErrors = {
def collect (e: Throwable): Seq[Throwable] = e match {
def collectExceptions(a: Throwable, b: Throwable): RequestErrors = {
def collect(e: Throwable): Seq[Throwable] = e match {
case RequestErrors(errors) => errors
case other => Seq(other)
}
Expand All @@ -98,7 +96,7 @@ trait RequestReader[A] { self =>
/**
* Applies the given filter `p` to this request reader.
*/
def withFilter(p: A => Boolean): RequestReader[A] = self.should("not fail validation")(p)
def withFilter(p: A => Boolean): PRequestReader[R, A] = self.should("not fail validation")(p)

/**
* Validates the result of this request reader using a `predicate`. The rule is used for error reporting.
Expand All @@ -109,7 +107,7 @@ trait RequestReader[A] { self =>
* @return a request reader that will return the value of this reader if it is valid.
* Otherwise the future fails with a [[io.finch.request.NotValid NotValid]] error.
*/
def should(rule: String)(predicate: A => Boolean): RequestReader[A] = embedFlatMap { a =>
def should(rule: String)(predicate: A => Boolean): PRequestReader[R, A] = embedFlatMap { a =>
if (predicate(a)) a.toFuture
else NotValid(self.item, "should " + rule).toFutureException
}
Expand All @@ -123,7 +121,7 @@ trait RequestReader[A] { self =>
* @return a request reader that will return the value of this reader if it is valid.
* Otherwise the future fails with a [[io.finch.request.NotValid NotValid]] error.
*/
def shouldNot(rule: String)(predicate: A => Boolean): RequestReader[A] = should(s"not $rule.")(x => !predicate(x))
def shouldNot(rule: String)(predicate: A => Boolean): PRequestReader[R, A] = should(s"not $rule.")(x => !predicate(x))

/**
* Validates the result of this request reader using a predefined `rule`. This method allows for rules to be reused
Expand All @@ -135,7 +133,7 @@ trait RequestReader[A] { self =>
* @return a request reader that will return the value of this reader if it is valid.
* Otherwise the future fails with a [[io.finch.request.NotValid NotValid]] error.
*/
def should(rule: ValidationRule[A]): RequestReader[A] = should(rule.description)(rule.apply)
def should(rule: ValidationRule[A]): PRequestReader[R, A] = should(rule.description)(rule.apply)

/**
* Validates the result of this request reader using a predefined `rule`. This method allows for rules to be reused
Expand All @@ -147,7 +145,7 @@ trait RequestReader[A] { self =>
* @return a request reader that will return the value of this reader if it is valid.
* Otherwise the future fails with a [[io.finch.request.NotValid NotValid]] error.
*/
def shouldNot(rule: ValidationRule[A]): RequestReader[A] = shouldNot(rule.description)(rule.apply)
def shouldNot(rule: ValidationRule[A]): PRequestReader[R, A] = shouldNot(rule.description)(rule.apply)
}

/**
Expand All @@ -159,47 +157,39 @@ object RequestReader {
* Creates a new [[io.finch.request.RequestReader RequestReader]] that always succeeds, producing the specified value.
*
* @param value the value the new reader should produce
* @param item the request item (e.g. parameter, header) the value is associated with
* @return a new reader that always succeeds, producing the specified value
*/
def value[A](value: A, item: RequestItem = MultipleItems): RequestReader[A] =
const[A](value.toFuture)
def value[A](value: A): RequestReader[A] = const[A](value.toFuture)

/**
* Creates a new [[io.finch.request.RequestReader RequestReader]] that always fails, producing the specified
* exception.
*
* @param exc the exception the new reader should produce
* @param item the request item (e.g. parameter, header) the value is associated with
* @return a new reader that always fails, producing the specified exception
*/
def exception[A](exc: Throwable, item: RequestItem = MultipleItems): RequestReader[A] =
const[A](exc.toFutureException)
def exception[A](exc: Throwable): RequestReader[A] = const[A](exc.toFutureException)

/**
* Creates a new [[io.finch.request.RequestReader RequestReader]] that always produces the specified value. It will
* succeed if the given `Future` succeeds and fail if the `Future` fails.
*
* @param value the value the new reader should produce
* @param item the request item (e.g. parameter, header) the value is associated with
* @return a new reader that always produces the specified value
*/
def const[A](value: Future[A], item: RequestItem = MultipleItems): RequestReader[A] =
embed[A](item)(_ => value)
def const[A](value: Future[A]): RequestReader[A] = embed[HttpRequest, A](MultipleItems)(_ => value)

/**
* Creates a new [[io.finch.request.RequestReader RequestReader]] that reads the result from the request.
*
* @param item the request item (e.g. parameter, header) the value is associated with
* @param f the function to apply to the request
* @return a new reader that reads the result from the request
*/
def apply[A](item: RequestItem)(f: HttpRequest => A): RequestReader[A] =
embed[A](item)(f(_).toFuture)
def apply[R, A](f: R => A): PRequestReader[R, A] = embed[R, A](MultipleItems)(f(_).toFuture)

private[this] def embed[A](reqItem: RequestItem)(f: HttpRequest => Future[A]): RequestReader[A] =
new RequestReader[A] {
val item = reqItem
def apply[Req](req: Req)(implicit ev: Req => HttpRequest): Future[A] = f(req)
private[request] def embed[R, A](i: RequestItem)(f: R => Future[A]): PRequestReader[R, A] =
new PRequestReader[R, A] {
val item = i
def apply(req: R): Future[A] = f(req)
}
}
Loading

0 comments on commit e6793fb

Please sign in to comment.