Skip to content

Commit

Permalink
Merge pull request #33 from scala-exercises/eval-32-compatibility-sca…
Browse files Browse the repository at this point in the history
…lajs

Evaluator Client compatibility with ScalaJS
  • Loading branch information
juanpedromoreno committed Oct 7, 2016
2 parents 5fa54cd + 758b7b3 commit 5f8e7f0
Show file tree
Hide file tree
Showing 21 changed files with 276 additions and 333 deletions.
54 changes: 40 additions & 14 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -1,38 +1,65 @@
lazy val noPublishSettings = Seq(
publish := (),
publishLocal := (),
publishArtifact := false
)

lazy val root = (project in file("."))
.settings(mainClass in Universal := Some("org.scalaexercises.evaluator.EvaluatorServer"))
.settings(stage <<= (stage in Universal in `evaluator-server`))
.aggregate(`evaluator-server`, `evaluator-shared`, `evaluator-client`)
.settings(noPublishSettings: _*)
.aggregate(`evaluator-server`, `evaluator-shared-jvm`, `evaluator-shared-js`, `evaluator-client-jvm`, `evaluator-client-js`)

lazy val `evaluator-shared` = (project in file("shared"))
lazy val `evaluator-shared` = (crossProject in file("shared"))
.enablePlugins(AutomateHeaderPlugin)
.settings(name := "evaluator-shared")

lazy val `evaluator-client` = (project in file("client"))
lazy val `evaluator-shared-jvm` = `evaluator-shared`.jvm
lazy val `evaluator-shared-js` = `evaluator-shared`.js

lazy val scalaJSSettings = Seq(
requiresDOM := false,
scalaJSUseRhino := false,
jsEnv := NodeJSEnv().value,
libraryDependencies ++= Seq(
"fr.hmil" %%% "roshttp" % v('roshttp),
"org.typelevel" %%% "cats-free" % v('cats),
"io.circe" %%% "circe-core" % v('circe),
"io.circe" %%% "circe-generic" % v('circe),
"io.circe" %%% "circe-parser" % v('circe)
)
)

lazy val `evaluator-client` = (crossProject in file("client"))
.dependsOn(`evaluator-shared`)
.enablePlugins(AutomateHeaderPlugin)
.settings(
name := "evaluator-client",
libraryDependencies <++= libraryVersions { v => Seq(
libraryDependencies ++= Seq(
"fr.hmil" %% "roshttp" % v('roshttp),
"org.typelevel" %% "cats-free" % v('cats),
"io.circe" %% "circe-core" % v('circe),
"io.circe" %% "circe-generic" % v('circe),
"io.circe" %% "circe-parser" % v('circe),
"io.circe" %% "circe-core" % v('circe),
"io.circe" %% "circe-generic" % v('circe),
"io.circe" %% "circe-parser" % v('circe),
"org.log4s" %% "log4s" % v('log4s),
"org.scalaj" %% "scalaj-http" % v('scalajhttp),
"org.slf4j" % "slf4j-simple" % v('slf4j),
// Testing libraries
"org.scalatest" %% "scalatest" % v('scalaTest) % "test"
)
}
)
)
.jsSettings(scalaJSSettings: _*)

lazy val `evaluator-client-jvm` = `evaluator-client`.jvm
lazy val `evaluator-client-js` = `evaluator-client`.js

lazy val `evaluator-server` = (project in file("server"))
.dependsOn(`evaluator-shared`)
.dependsOn(`evaluator-shared-jvm`)
.enablePlugins(JavaAppPackaging)
.enablePlugins(AutomateHeaderPlugin)
.settings(noPublishSettings: _*)
.settings(
name := "evaluator-server",
libraryDependencies <++= libraryVersions { v => Seq(
libraryDependencies ++= Seq(
"io.monix" %% "monix" % v('monix),
"org.http4s" %% "http4s-dsl" % v('http4s),
"org.http4s" %% "http4s-blaze-server" % v('http4s),
Expand All @@ -49,9 +76,8 @@ lazy val `evaluator-server` = (project in file("server"))
"io.get-coursier" %% "coursier-cache" % v('coursier),
"org.scalatest" %% "scalatest" % v('scalaTest) % "test"
)
}
)
.settings(compilerDependencySettings: _*)

onLoad in Global := (Command.process("project evaluator-server", _: State)) compose (onLoad in Global).value
addCommandAlias("publishSignedAll", ";evaluator-shared/publishSigned;evaluator-client/publishSigned")
addCommandAlias("publishSignedAll", ";evaluator-sharedJS/publishSigned;evaluator-sharedJVM/publishSigned;evaluator-clientJS/publishSigned;evaluator-clientJVM/publishSigned")
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* scala-exercises-evaluator-client
* Copyright (C) 2015-2016 47 Degrees, LLC. <http://www.47deg.com>
*/

package org.scalaexercises.evaluator

import cats.free.Free
import org.scalaexercises.evaluator.EvaluatorResponses.EvaluationResponse
import org.scalaexercises.evaluator.free.algebra.EvaluatorOps

class EvaluatorAPI[F[_]](url: String, authKey: String)(
implicit O: EvaluatorOps[F]) {

def evaluates(resolvers: List[String] = Nil,
dependencies: List[Dependency] = Nil,
code: String): Free[F, EvaluationResponse[EvalResponse]] =
O.evaluates(url, authKey, resolvers, dependencies, code)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* scala-exercises-evaluator-client
* Copyright (C) 2015-2016 47 Degrees, LLC. <http://www.47deg.com>
*/

package org.scalaexercises.evaluator

import cats.data.XorT
import cats.~>
import cats._, cats.std.all._
import org.scalaexercises.evaluator.EvaluatorResponses.{EvalIO, EvaluationException, EvaluationResponse, EvaluationResult}
import org.scalaexercises.evaluator.free.algebra.EvaluatorOp

import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global

class EvaluatorClient(url: String, authKey: String) {

lazy val api: EvaluatorAPI[EvaluatorOp] = new EvaluatorAPI(url, authKey)

}

object EvaluatorClient {

def apply(url: String, authKey: String) =
new EvaluatorClient(url, authKey)

implicit class EvaluationIOSyntaxXOR[A](
evalIO: EvalIO[EvaluationResponse[A]]) {

def exec(
implicit I: (EvaluatorOp ~> Future)): Future[EvaluationResponse[A]] =
evalIO foldMap I

def liftEvaluator: XorT[EvalIO, EvaluationException, EvaluationResult[A]] =
XorT[EvalIO, EvaluationException, EvaluationResult[A]](evalIO)

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* scala-exercises-evaluator-client
* Copyright (C) 2015-2016 47 Degrees, LLC. <http://www.47deg.com>
*/

package org.scalaexercises.evaluator

import cats.data.Xor
import cats.free.Free
import cats.syntax.xor._
import io.circe.Decoder
import io.circe.parser._
import io.circe.generic.auto._
import org.scalaexercises.evaluator.free.algebra.EvaluatorOp

import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global

import fr.hmil.roshttp.HttpResponse
import fr.hmil.roshttp.HeaderMap

object EvaluatorResponses {

type EvalIO[A] = Free[EvaluatorOp, A]

type EvaluationResponse[A] = EvaluationException Xor EvaluationResult[A]

case class EvaluationResult[A](result: A,
statusCode: Int,
headers: Map[String, String])

sealed abstract class EvaluationException(msg: String,
cause: Option[Throwable] = None)
extends Throwable(msg) {
cause foreach initCause
}

case class JsonParsingException(msg: String, json: String)
extends EvaluationException(msg)

case class UnexpectedException(msg: String) extends EvaluationException(msg)

def toEntity[A](futureResponse: Future[HttpResponse])(
implicit D: Decoder[A]): Future[EvaluationResponse[A]] =
futureResponse map {
case r if isSuccess(r.statusCode)
decode[A](r.body).fold(
e
JsonParsingException(e.getMessage, r.body)
.left[EvaluationResult[A]],
result
Xor.Right(
EvaluationResult(result, r.statusCode, r.headers.toLowerCase))
)
case r
UnexpectedException(
s"Failed invoking get with status : ${r.statusCode}, body : \n ${r.body}")
.left[EvaluationResult[A]]
}

private[this] def isSuccess(statusCode: Int) =
statusCode >= 200 && statusCode <= 299

implicit class HeadersLowerCase[A >: String](headers: HeaderMap[A]) {

def toLowerCase: Map[String, A] =
headers.iterator.map(t => (t._1.toLowerCase, t._2)).toList.toMap
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import org.scalaexercises.evaluator.http.HttpClient
import io.circe.generic.auto._
import io.circe.syntax._

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

class Evaluator {

Expand All @@ -21,16 +21,12 @@ class Evaluator {

def eval(url: String,
authKey: String,
connTimeout: Duration,
readTimeout: Duration,
resolvers: List[String] = Nil,
dependencies: List[Dependency] = Nil,
code: String): EvaluationResponse[EvalResponse] =
code: String): Future[EvaluationResponse[EvalResponse]] =
httpClient.post[EvalResponse](
url = url,
secretKey = authKey,
connTimeout = connTimeout,
readTimeout = readTimeout,
data = EvalRequest(resolvers, dependencies, code).asJson.noSpaces)

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,9 @@ import cats.free.{Free, Inject}
import org.scalaexercises.evaluator.{Dependency, EvalResponse}
import org.scalaexercises.evaluator.EvaluatorResponses.EvaluationResponse

import scala.concurrent.duration._
import scala.concurrent.duration.Duration

sealed trait EvaluatorOp[A]
final case class Evaluates(url: String,
authKey: String,
connTimeout: Duration,
readTimeout: Duration,
resolvers: List[String] = Nil,
dependencies: List[Dependency] = Nil,
code: String)
Expand All @@ -27,21 +22,12 @@ class EvaluatorOps[F[_]](implicit I: Inject[EvaluatorOp, F]) {
def evaluates(
url: String,
authKey: String,
connTimeout: Duration,
readTimeout: Duration,
resolvers: List[String] = Nil,
dependencies: List[Dependency] = Nil,
code: String
): Free[F, EvaluationResponse[EvalResponse]] =
Free.inject[EvaluatorOp, F](
Evaluates(
url,
authKey,
connTimeout,
readTimeout,
resolvers,
dependencies,
code))
Evaluates(url, authKey, resolvers, dependencies, code))

}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* scala-exercises-evaluator-client
* Copyright (C) 2015-2016 47 Degrees, LLC. <http://www.47deg.com>
*/

package org.scalaexercises.evaluator.free.interpreters

import cats.~>
import org.scalaexercises.evaluator.api.Evaluator
import org.scalaexercises.evaluator.free.algebra.{Evaluates, EvaluatorOp}

import scala.concurrent.Future

trait Interpreter {

/**
* Lifts Evaluator Ops to an effect capturing Monad such as Task via natural transformations
*/
implicit def evaluatorOpsInterpreter: EvaluatorOp ~> Future =
new (EvaluatorOp ~> Future) {

val evaluator = new Evaluator()

def apply[A](fa: EvaluatorOp[A]): Future[A] = fa match {
case Evaluates(url, authKey, resolvers, dependencies, code)
evaluator.eval(url, authKey, resolvers, dependencies, code)
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ import io.circe.Decoder
import org.scalaexercises.evaluator.EvaluatorResponses
import org.scalaexercises.evaluator.EvaluatorResponses.EvaluationResponse

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

object HttpClient {

Expand All @@ -26,17 +25,11 @@ class HttpClient {
url: String,
secretKey: String,
method: String = "post",
connTimeout: Duration,
readTimeout: Duration,
headers: Headers = Map.empty,
data: String
)(implicit D: Decoder[A]): EvaluationResponse[A] =
)(implicit D: Decoder[A]): Future[EvaluationResponse[A]] =
EvaluatorResponses.toEntity(
HttpRequestBuilder(
url = url,
httpVerb = method,
connTimeout = connTimeout,
readTimeout = readTimeout)
HttpRequestBuilder(url = url, httpVerb = method)
.withHeaders(headers + (authHeaderName -> secretKey))
.withBody(data)
.run)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* scala-exercises-evaluator-client
* Copyright (C) 2015-2016 47 Degrees, LLC. <http://www.47deg.com>
*/

package org.scalaexercises.evaluator.http

import org.scalaexercises.evaluator.http.HttpClient._

import scala.concurrent.Future

import fr.hmil.roshttp.{HttpRequest, Method, HttpResponse}
import fr.hmil.roshttp.body.BodyPart

import java.nio.ByteBuffer

case class HttpRequestBuilder(
url: String,
httpVerb: String,
headers: Headers = Map.empty[String, String],
body: String = ""
) {

case class CirceJSONBody(value: String) extends BodyPart {
override def contentType: String = s"application/json; charset=utf-8"

override def content: ByteBuffer = ByteBuffer.wrap(value.getBytes("utf-8"))
}

def withHeaders(headers: Headers) = copy(headers = headers)

def withBody(body: String) = copy(body = body)

def run: Future[HttpResponse] = {

val request = HttpRequest(url)
.withMethod(Method(httpVerb))
.withHeader("content-type", "application/json")
.withHeaders(headers.toList: _*)

request.send(CirceJSONBody(body))
}
}
Loading

0 comments on commit 5f8e7f0

Please sign in to comment.