Skip to content

Instantly share code, notes, and snippets.

@mgosk
Last active August 29, 2015 14:12
Show Gist options
  • Save mgosk/25b9f01abf98eae159d1 to your computer and use it in GitHub Desktop.
Save mgosk/25b9f01abf98eae159d1 to your computer and use it in GitHub Desktop.
package com.foobar.core.routing.directives
import com.iterators.config.CakeConfig
import shapeless.{HNil, ::}
import spray.http.DateTime
import spray.routing._
import scala.concurrent.ExecutionContext
import scala.reflect.ClassTag
import scala.reflect.runtime.{currentMirror => cm}
import scala.reflect.runtime.universe._
import scala.slick.lifted.MappedTo
import scala.util.{Left, Right}
case class MalformedParameterRejection(paramName: String) extends Rejection
case class MissingParameterRejection(paramName: String) extends Rejection
trait ParamsExtractorDirective extends HttpService {
def logger = CakeConfig.logger
// main directive
def extractParams[T](magnet: ParamsMagnet[T]): Directive1[T] = magnet.directive
// container for extraced data
type ParamsContainer[T] = Either[Rejection, T]
// type for selecting correct magnet
class ToCaseClassExtractor[T]
implicit def dupa[T]: ToCaseClassExtractor[T] = new ToCaseClassExtractor
def as[T](implicit pe: ToCaseClassExtractor[T]) = pe
class ParamsMagnet[T](paramsDirective: Directive1[T]) {
val directive: Directive1[T] = paramsDirective
}
object ParamsMagnet {
implicit def fromType[T](u: ToCaseClassExtractor[T])(implicit executor: ExecutionContext, C: ClassTag[T], ttag: TypeTag[T]): ParamsMagnet[T] = {
new ParamsMagnet(
new Directive1[T] {
override def happly(f: (::[T, HNil]) => Route): Route = {
ctx => {
extract[T](ctx) match {
case Right(t: T) ⇒ f(t :: HNil)(ctx)
case Left(r:Rejection) ⇒ ctx.reject(r)
}
}
}
}
)
}
}
def extract[T](ctx: RequestContext)(implicit C: ClassTag[T], ttag: TypeTag[T]): ParamsContainer[T] = {
val obj = default[T](ctx)
imbue(obj)(ctx)
}
def default[T](ctx: RequestContext)(implicit C: ClassTag[T], ttag: TypeTag[T]): T = {
val claas: ClassSymbol = cm.classSymbol(C.runtimeClass)
val instanceMirror: InstanceMirror = cm.reflect((cm.reflectModule(claas.companion.asModule)).instance)
val typeSignature: Type = instanceMirror.symbol.typeSignature
val applyMethod: MethodSymbol = typeSignature.member(TermName("apply")).asMethod
def valueFor(p: Symbol, i: Int): Any = {
// val field = p.name.toString //name of extracted field
// val value = ctx.request.uri.query.get(field) //extracted value of field
// logger.info(s"field:${field} value:${value}")
val defarg: Symbol = typeSignature.member(TermName(s"apply$$default$$${i + 1}"))
if (defarg != NoSymbol) {
// logger.info(s"default $defarg")
(instanceMirror.reflectMethod(defarg.asMethod))()
} else {
// logger.info(s"def val for $p")
p.typeSignature match {
case t if t =:= typeOf[String] => null
case t if t =:= typeOf[Int] => 0
case x => throw new IllegalArgumentException(x.toString)
}
}
}
//collect args
val args = (for (ps <- applyMethod.paramLists;
p <- ps) yield p).zipWithIndex map (p => valueFor(p._1, p._2))
//create instance
(instanceMirror.reflectMethod(applyMethod))(args: _*).asInstanceOf[T]
}
private def imbue[T](obj: T)(ctx: RequestContext)(implicit C: ClassTag[T], ttag: TypeTag[T]): ParamsContainer[T] = {
val m = runtimeMirror(obj.getClass.getClassLoader)
val im = m.reflect(obj)
val fieldSymbols = typeOf[T].members.collect {
case m: MethodSymbol if m.isGetter => m
}
fieldSymbols.map { field =>
val fName = field.name.decodedName.toString
val fValue = ctx.request.uri.query.get(fName)
val fType: Type = field.typeSignature.resultType
val termSymb = typeOf[T].decl(TermName(fName)).asTerm
val fieldMirror = im.reflectField(termSymb)
fType match {
case t if t =:= typeOf[String] => {
logger.info("string")
if (fValue.isEmpty)
return Left(MissingParameterRejection(fName))
fieldMirror.set(fValue.get)
}
case t if t =:= typeOf[Option[String]] && fValue.isDefined => {
logger.info("Option[String]")
fieldMirror.set(fValue)
}
case t if t =:= typeOf[DateTime] => {
logger.info("DateTime")
if (fValue.isEmpty)
return Left(MissingParameterRejection(fName))
val value: DateTime = DateTime.fromIsoDateTimeString(fValue.get).getOrElse(return Left(MalformedParameterRejection(fName)))
fieldMirror.set(value)
}
case t if t =:= typeOf[Option[DateTime]] && fValue.isDefined => {
logger.info("Option[DateTime]")
val value = DateTime.fromIsoDateTimeString(fValue.get)
fieldMirror.set(value)
}
case t if t =:= typeOf[Int] => {
logger.info("Int")
if (fValue.isEmpty)
return Left(MissingParameterRejection(fName))
val value = fValue.get.toInt
fieldMirror.set(value)
}
case t if t =:= typeOf[Option[Int]] && fValue.isDefined => {
logger.info("Option[Int]")
val value = Some(fValue.get.toInt)
fieldMirror.set(value)
}
case t if t <:< typeOf[MappedTo[String]] => {
logger.info("MappedTo[String]")
val value = fValue.get
// fieldMirror.set(value)
}
case x => {
logger.info("other")
}
}
logger.info(s"N:${fName} V:${fValue} T:${fType}")
}
Right(obj)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment