Skip to content

Commit

Permalink
Move implementation into the type EndpointExtractor class
Browse files Browse the repository at this point in the history
  • Loading branch information
akozhemiakin committed Nov 25, 2016
1 parent d02bc6b commit ed507ff
Show file tree
Hide file tree
Showing 10 changed files with 143 additions and 85 deletions.
1 change: 0 additions & 1 deletion .travis.yml
Expand Up @@ -2,7 +2,6 @@ language: scala
sudo: false
scala:
- 2.11.8
- 2.10.6
jdk:
- openjdk7
- oraclejdk7
Expand Down
6 changes: 3 additions & 3 deletions README.md
Expand Up @@ -25,7 +25,7 @@ libraryDependencies ++= Seq(
resolvers += Resolver.sonatypeRepo("snapshots")

libraryDependencies ++= Seq(
"ru.arkoit" %% "finchrich-controller" % "0.1.3-SNAPSHOT"
"ru.arkoit" %% "finchrich-controller" % "0.2.0-SNAPSHOT"
)
```

Expand All @@ -46,7 +46,7 @@ object MyAwesomeController extends Controller {
}

// Get the coproduct of all endpoints from the MyAwesomeControlller
val ep = controllerToEndpoint(MyAwesomeController)
val ep = MyAwesomeController.toEndpoint
```

Also it allows you to nest controllers like this:
Expand All @@ -71,7 +71,7 @@ object MainController extends Controller {
}

// Get the coproduct of all endpoints from the MainController and nested controllers
val ep = controllerToEndpoint(MainController)
val ep = MainController.toEndpoint
```

All of this stuff does not use any kind of runtime reflection and is
Expand Down
6 changes: 3 additions & 3 deletions build.sbt
@@ -1,18 +1,18 @@
lazy val versions = new {
val scala = "2.11.8"
val finch = "0.11.0-M3"
val finch = "0.11.0-M4"
}

lazy val commonSettings = Seq (
version := "0.1.3-SNAPSHOT",
version := "0.2.0-SNAPSHOT",
organization := "ru.arkoit",
scalaVersion := versions.scala,
libraryDependencies ++= Seq(
"org.scalatest" %% "scalatest" % "2.2.4" % "test",
compilerPlugin("org.scalamacros" % "paradise" % "2.1.0" cross CrossVersion.full),
"org.scala-lang" % "scala-compiler" % scalaVersion.value % "provided"
),
crossScalaVersions := Seq("2.11.8", "2.10.6"),
crossScalaVersions := Seq("2.11.8"),
scalacOptions ++= Seq("-feature", "-language:implicitConversions"),
resolvers ++= Seq(
Resolver.sonatypeRepo("releases"),
Expand Down
@@ -0,0 +1,32 @@
package ru.arkoit.finchrich.controller

import ru.arkoit.finchrich.controller.internal.FinchRichMacro
import scala.language.experimental.macros
import io.finch.Endpoint

trait EndpointExtractor[A <: Controller] {
type R

def apply(c: A): Endpoint[R]
}

object EndpointExtractor {
type Aux[C <: Controller, RR] = EndpointExtractor[C] { type R = RR }

def apply[A <: Controller](implicit t: EndpointExtractor[A]): Aux[A, t.R] = t

implicit def materialize[A <: Controller, R]: Aux[A, R] =
macro FinchRichMacro.materialize[A, R]

trait Ops[A <: Controller, R] {
def toEndpoint: Endpoint[R]
}

trait ToOps {
implicit def toControllerToEndpointOps[A <: Controller]
(c: A)(implicit cte: EndpointExtractor[A]): Ops[A, cte.R] =
new Ops[A, cte.R] {
override def toEndpoint: Endpoint[cte.R] = cte(c)
}
}
}
Expand Up @@ -9,8 +9,7 @@ import macrocompat.bundle
private[finchrich] class FinchRichMacro(val c: whitebox.Context) {
import c.universe._

def controllerToEndpoint[T <: Controller : c.WeakTypeTag](cnt: c.Expr[T]): Tree = {

def materialize[T <: Controller : c.WeakTypeTag, R : c.WeakTypeTag]: Tree = {
def symbolResultType(s: Symbol): Type = s match {
case x if x.isMethod => x.asMethod.returnType
case x => x.typeSignature
Expand All @@ -20,35 +19,47 @@ private[finchrich] class FinchRichMacro(val c: whitebox.Context) {
t.members
.filter(x => x.isTerm && x.isPublic && !x.isSynthetic)
.map(_.asTerm)
.filter{
.filter {
case x if x.isMethod =>
val ms = x.asMethod
!ms.isConstructor && (ms.returnType <:< c.weakTypeOf[Controller] | ms.returnType <:< c.weakTypeOf[Endpoint[_]])
case x if !x.isMethod => x.typeSignature <:< c.weakTypeOf[Controller] | x.typeSignature <:< c.weakTypeOf[Endpoint[_]]
case _ => false
}

def extract(t: Type, context: Tree): List[Tree] = {
def extract(t: Type, context: Tree): List[(Tree, Type)] = {
filterApplicableTerms(t).toList.flatMap{
case x if symbolResultType(x) <:< c.weakTypeOf[Controller] => extract(symbolResultType(x), q"$context.${x.name.toTermName}")
case x => List(q"$context.${x.name.toTermName}")
case x => List((q"$context.${x.name.toTermName}", symbolResultType(x).typeArgs.head))
}
}

val v = extract(c.weakTypeOf[T], q"$cnt").foldLeft(q"": Tree)((a, b) => a match {
val (exSyms, exTypes) = extract(c.weakTypeOf[T], q"a").unzip

if (exSyms.isEmpty)
c.abort(c.enclosingPosition, "Controller passed to the controllerToEndpoint function does not contain neither endpoints nor other non-empty controllers.")

val result = exSyms.foldLeft(q"": Tree)((a, b) => a match {
case q"" => q"$b"
case x => q"$x :+: $b"
}) match {
case q"" => c.abort(c.enclosingPosition, "Controller passed to the controllerToEndpoint function does not contain neither endpoints nor other non-empty controllers.")
case x => x
}
case _ => q"$a.:+:($b)"
})

val resultType = if (exTypes.length == 1) q"${exTypes.head}" else
exTypes.reverse.foldRight(tq"": Tree)((a, b) => b match {
case tq"" => tq":+:[$a, CNil]"
case _ => tq":+:[$a, $b]"
})

q"""
import io.finch._
import io.finch.Endpoint
import shapeless._
import ru.arkoit.finchrich._
import ru.arkoit.finchrich.controller.EndpointExtractor

new EndpointExtractor[${c.weakTypeOf[T]}] {
type R = $resultType

$v
def apply(a: ${c.weakTypeOf[T]}): Endpoint[R] = $result
}
"""
}
}
@@ -1,8 +1,10 @@
package ru.arkoit.finchrich

import ru.arkoit.finchrich.controller.internal.FinchRichMacro
import scala.language.experimental.macros
import io.finch.Endpoint

package object controller {
def controllerToEndpoint[T <: Controller](cnt: T): Any = macro FinchRichMacro.controllerToEndpoint[T]
package object controller extends EndpointExtractor.ToOps {
@deprecated("Use toEndpoint on the controller instance instead", "0.2.0")
def controllerToEndpoint[T <: Controller](cnt: T)
(implicit cte: EndpointExtractor[T]): Endpoint[cte.R] =
cte(cnt)
}
@@ -0,0 +1,15 @@
package ru.arkoit.finchrich.controller

import io.finch._
import org.scalatest.{FlatSpec, Matchers}

class DeprecatedFinchRichSpec extends FlatSpec with Matchers with Fixtures {
behavior of "controllerToEndpoint (deprecated)"

it should "transform controller to an endpoint" in {
val cnt = new ComplexController()
val ep = controllerToEndpoint(cnt)
checkEndpointType(ep)
ep.toServiceAs[Text.Plain]
}
}
@@ -0,0 +1,15 @@
package ru.arkoit.finchrich.controller

import io.finch._
import org.scalatest.{FlatSpec, Inside, Matchers}

class EndpointExtractorSpec extends FlatSpec with Matchers with Fixtures {
behavior of "EndpointExtractor"

it should "transform controller to an endpoint" in {
val cnt = new ComplexController()
val ep = cnt.toEndpoint
checkEndpointType(ep)
ep.toServiceAs[Text.Plain]
}
}

This file was deleted.

@@ -0,0 +1,44 @@
package ru.arkoit.finchrich.controller

import shapeless._
import io.finch._
import scala.reflect.runtime.universe._

trait Fixtures {
implicit def universalEncoder[A](implicit tenc: Encode.Aux[String, Text.Plain]): Encode.Aux[A, Text.Plain] =
Encode.text[A]((a, c) => tenc(a.toString, c))

type ComplexControllerEPType = Boolean :+: String :+: Int :+: Int :+: String :+: Unit :+: String :+: CNil

class ControllerA extends Controller {
val ep = get("foo") { Ok(10) }
def ep2 = get("bar" :: string) { s: String => Ok(s)}
}

class ControllerB extends Controller {
val ep = get("hehe") { Ok(true) }
val ep2 = get("jjj") { Ok("hhh") }
}

case class CaseClassController (
ep: Endpoint[Int],
cnt: ControllerA
) extends Controller

class ComplexController extends Controller {
val cnt = new ControllerB
val cnt2 = CaseClassController(
ep = get("baz") { Ok(10) },
cnt = new ControllerA
)
val ep = get("hojjk") { Ok() }
val ep2 = get("mmmmm") { Ok("koko") }
}

def checkEndpointType[T: WeakTypeTag](b: Endpoint[T]): Boolean = {
weakTypeOf[T] match {
case x if x =:= weakTypeOf[ComplexControllerEPType] => true
case _ => false
}
}
}

0 comments on commit ed507ff

Please sign in to comment.