Last active
August 29, 2015 14:12
-
-
Save mgosk/25b9f01abf98eae159d1 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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