Skip to content
This repository has been archived by the owner on Feb 19, 2021. It is now read-only.

Commit

Permalink
Merge pull request #2 from kailuowang/master
Browse files Browse the repository at this point in the history
getting ready for release.
  • Loading branch information
kailuowang committed Nov 25, 2015
2 parents 5e481e8 + fce606f commit 091922a
Show file tree
Hide file tree
Showing 17 changed files with 236 additions and 88 deletions.
9 changes: 6 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
language: scala

sudo: required
sudo: false

services:
- docker
Expand All @@ -17,5 +17,8 @@ cache:
- $HOME/.ivy2/cache
- $HOME/.sbt/boot/

script: "sbt clean coverage test"
after_success: "sbt coveralls"
script:
- sbt clean coverage test &&
sbt coverageAggregate
after_success:
- sbt coveralls
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package com.iheart.play.dsl.akka
package com.iheart.play.dsl

import akka.actor.{ ActorSelection, ActorRef }
import akka.util.Timeout
import akka.pattern.ask
import _root_.akka.actor.{ ActorSelection, ActorRef }
import _root_.akka.util.Timeout
import _root_.akka.pattern.ask
import com.iheart.play.dsl.Syntax._

object Syntax {
package object akka {
implicit def actorAskBuilder(implicit to: Timeout) = new AskableBuilder[ActorRef] {
def apply(t: ActorRef): Askable = t.ask
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.iheart.play.dsl

import cats.Monad
import cats.functor.Contravariant

import scala.concurrent.Future

object CatsInstances {

implicit val futureMonad: Monad[Future] = new Monad[Future] {
import scala.concurrent.ExecutionContext.Implicits.global

def pure[A](x: A): Future[A] = Future.successful(x)

def flatMap[A, B](fa: Future[A])(f: (A) Future[B]): Future[B] = fa flatMap f
}

implicit def partialFunctionContravariant[R]: Contravariant[PartialFunction[?, R]] =
new Contravariant[PartialFunction[?, R]] {
def contramap[T1, T0](pa: PartialFunction[T1, R])(f: T0 T1) = new PartialFunction[T0, R] {
def isDefinedAt(x: T0): Boolean = pa.isDefinedAt(f(x))
def apply(x: T0): R = pa(f(x))
}
}

}
13 changes: 10 additions & 3 deletions play-dsl-core/src/main/scala/com/iheart/play/dsl/Directive.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,23 @@ import play.api.mvc.Results._

import scala.concurrent.Future
import scala.reflect._
import CatsInstances._
import cats.std.function._
import cats.syntax.contravariant._

object Directive {

def constant[RMT](r: Result): Directive[RMT] = _ Future.successful(r)

def apply[RMT](pf: PartialFunction[RMT, Future[Result]]): Directive[RMT] = (req: Request[RMT]) pf(req.body)
def apply[RMT](f: RMT Result): Directive[RMT] = (f andThen Future.successful).contramap[Request[RMT]](_.body)

def synced[RMT](pf: PartialFunction[RMT, Result]): Directive[RMT] = apply(pf andThen (Future.successful))
}

object PartialDirective {

def apply[RMT](pf: PartialFunction[RMT, Future[Result]]): PartialDirective[RMT] = pf.contramap[Request[RMT]](_.body)

def apply[RMT](f: RMT Result): Directive[RMT] = apply(PartialFunction(f andThen Future.successful))
def synced[RMT](pf: PartialFunction[RMT, Result]): PartialDirective[RMT] = apply(pf andThen (Future.successful))

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ object Extractor {
}

trait ExtractorOps {
import ScalaCatsInstances._
import CatsInstances._

implicit class extractorOps[Repr <: HList](self: Extractor[Repr]) {
def and[ThatR <: HList, ResultR <: HList](that: Extractor[ThatR])(
Expand Down

This file was deleted.

41 changes: 30 additions & 11 deletions play-dsl-core/src/main/scala/com/iheart/play/dsl/Syntax.scala
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
package com.iheart.play.dsl

import com.iheart.play.dsl._
import com.iheart.play.dsl.directives.FallBackDir
import com.iheart.play.dsl.extractors.AuthInfoExtractorBuilder
import play.api.libs.json.{JsValue, Json, Writes, Reads}
import play.api.mvc.Results._
import play.api.mvc.{AnyContent, Request, Result}
import play.api.mvc.{RequestHeader, Result}
import shapeless.ops.hlist.{Align, ZipWithKeys}
import shapeless.ops.record.{Values, Keys}
import shapeless.{LabelledGeneric, HList}

import scala.concurrent.Future
import scala.reflect.ClassTag
import scala.util.Try
import cats.syntax.semigroup._

object Syntax
extends ProcessorOps
Expand All @@ -34,29 +31,51 @@ object Syntax

def process[RMT] = new processorBuilder[RMT]

// def ask[RMT, T](t: T)(implicit b: AskableBuilder[T]): Processor[RMT, Any] = Processor[RMT, Any](b(t))

implicit class DirectiveDSL[RMT: ClassTag](self: Directive[RMT]) {
def notFoundIfEmpty[InnerT](extractor: RMT Option[InnerT]): Directive[RMT] =
self.filter(filters.notFoundIfEmpty(extractor))

def `with`(f: Filter[RMT]) = self.filter(f)

def ifEmpty[InnerT](fieldExtractor: RMT Option[InnerT]) = new Object {
def respond(alternative: Result) = {
val f: Filter[RMT] = (req, result) fieldExtractor(req.body).fold(Future.successful(alternative))(_ result)
self.`with`(f)
}
}
}

def `with`[RMT](filters: Filter[Any]*)(directive: Directive[RMT]): Directive[RMT] =
directive.filter(filters.reduce(_ and _))


def fromJson[T: Reads] = new extractors.JsonBodyExtractorBuilder[T]

implicit class ProcessAnyDSL[RMT](self: Processor[RMT, Any]) {
implicit class ProcessAnyDSL[RMT, PRT](self: Processor[RMT, PRT]) {
def expectAny(pf: PartialFunction[Any, Result]) = self next Directive(pf)

def `then`[RT: ClassTag](d: Directive[RT])(implicit fb: FallBackDir) = self next (d fallback fb)
}

implicit class FilterDSL[RMT](self: Filter[RMT]) {
import Filter._
def and(that: Filter[RMT]) = self combine that
}

def expect[T: ClassTag: Writes](tr: JsValue Result)(implicit fb: FallBackDir): Directive[Any] =
Directive((t: T) tr(Json.toJson(t))).fallback(fb)


case class expect[RMT: ClassTag]() {
private def directive(f: RMT Result) = Directive(f)

def respond(result: Result) = directive(_ result)
def respondJson(tr: JsValue Result)(implicit writes: Writes[RMT]) = directive(t tr(Json.toJson(t)))


}

def from[Repr <: HList] = Extractor.apply[Repr] _

def fromAuthorized[AuthInfoT](ba: RequestHeader Future[Either[String, AuthInfoT]]) = new AuthInfoExtractorBuilder[AuthInfoT](ba)

def handleParams[RMT, FullRepr <: HList, K <: HList, V <: HList]
(directive: Directive[RMT])
(implicit lgen: LabelledGeneric.Aux[RMT, FullRepr],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,11 @@ import Syntax._
package object directives {

@implicitNotFound("You need to provide an implicit fall back directive to handle mismatches. You can use the default one by \n import com.iheart.play.akka.DefaultImplicits._ ")
type FallBackDir = Directive[Any]
type FallBackDir = PartialDirective[Any]

def fallBackTo500: FallBackDir = Directive.synced[Any] {
case e: Throwable InternalServerError(e.getMessage)
def fallBackTo500: FallBackDir = PartialDirective.synced[Any] {
case e: Throwable InternalServerError(Json.obj("error" e.getMessage))
case m InternalServerError(new MatchError(m).getMessage)
}

def simpleOk[T: ClassTag: Writes](implicit fb: FallBackDir): Directive[Any] =
Directive((t: T) Ok(Json.toJson(t))).fallback(fb)

def checkType[T: ClassTag](result: Result)(implicit fb: FallBackDir): Directive[Any] =
Directive.constant[T](result).fallback(fb)

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.iheart.play.dsl.extractors

import cats.data.Xor
import com.iheart.play.dsl.Extractor
import play.api.mvc.RequestHeader
import play.api.mvc.Results._
import shapeless.HList

import scala.concurrent.Future

class AuthInfoExtractorBuilder[AuthInfoT](buildAuthInfo: RequestHeader Future[Either[String, AuthInfoT]]) {
import scala.concurrent.ExecutionContext.Implicits.global

def apply[Repr <: HList](toRecord: AuthInfoT Repr): Extractor[Repr] = { req
buildAuthInfo(req).map {
case Left(msg) Xor.Left(Unauthorized(msg))
case Right(authInfo) Xor.Right(toRecord(authInfo))
}
}
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
package com.iheart.play.dsl

import org.joda.time.DateTime
import play.api.mvc.Results._
import play.api.cache.CacheApi

import scala.concurrent.Future
import scala.concurrent.duration.Duration

package object filters {

def notFoundIfEmpty[RMT, OT](
fieldExtractor: RMT Option[OT],
notFoundMessage: Option[String] = None
): Filter[RMT] = (req, result) {
def caching[RMT](duration: Duration)(implicit cache: CacheApi): Filter[RMT] =
(req, result) cache.getOrElse(req.body.toString)(result)

val notFoundResult = Future.successful(NotFound(notFoundMessage.getOrElse("")))
fieldExtractor(req.body).fold(notFoundResult)(_ result)
def eTag[T](getETag: T DateTime): Filter[T] = { (req, result)
import play.api.libs.concurrent.Execution.Implicits._
import play.api.http.HeaderNames.ETAG
val eTAGInRequest = req.headers.get(ETAG)
val rse = getETag(req.body)
eTAGInRequest match {
case Some(e) if e.toLong == rse.getMillis Future.successful(NotModified)
case _ result.map(_.withHeaders(ETAG rse.getMillis.toString))
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,14 @@ import scala.concurrent.Future

package object dsl {

type Directive[-RMT] = Request[RMT] Future[Result]

type Processor[-RMT, +PRT] = Request[RMT] Future[PRT]

type Directive[-RMT] = Processor[RMT, Result]

type PartialDirective[-RMT] = PartialFunction[Request[RMT], Future[Result]]



type Filter[-RMT] = (Request[RMT], Future[Result]) Future[Result]

type Extractor[+ExtractedRepr <: HList] = Request[AnyContent] Future[Xor[Result, ExtractedRepr]]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class DirectiveSpec extends PlaySpecification {
case _ Future.successful(InternalServerError)
}

val dir: Directive[Any] = Directive(pf)
val dir: PartialDirective[Any] = PartialDirective(pf)
dir(FakeRequest().withBody("expected")) must be_==(Ok).await
dir(FakeRequest().withBody("something else")) must be_==(InternalServerError).await

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class ExtractorSpec extends PlaySpecification {

}

"+ combines two extractor" >> { implicit ee: ExecutionEnv
"and combines two extractor" >> { implicit ee: ExecutionEnv

val extractor1 = fromJson[Foo].body

Expand Down
Loading

0 comments on commit 091922a

Please sign in to comment.