diff --git a/client/src/main/scala/org/scalaexercises/evaluator/EvaluatorAPI.scala b/client/src/main/scala/org/scalaexercises/evaluator/EvaluatorAPI.scala new file mode 100644 index 00000000..5b05f9d9 --- /dev/null +++ b/client/src/main/scala/org/scalaexercises/evaluator/EvaluatorAPI.scala @@ -0,0 +1,17 @@ +/* + * scala-exercises-evaluator-client + * Copyright (C) 2015-2016 47 Degrees, LLC. + */ + +package org.scalaexercises.evaluator + +import org.scalaexercises.evaluator.free.algebra.{EvaluatorOp, EvaluatorOps} + +class EvaluatorAPI(url: String, authKey: String)( + implicit O: EvaluatorOps[EvaluatorOp]) { + + def evaluates(resolvers: List[String] = Nil, + dependencies: List[Dependency] = Nil, + code: String) = + O.evaluates(url, authKey, resolvers, dependencies, code) +} diff --git a/client/src/main/scala/org/scalaexercises/evaluator/EvaluatorClient.scala b/client/src/main/scala/org/scalaexercises/evaluator/EvaluatorClient.scala new file mode 100644 index 00000000..19f7011e --- /dev/null +++ b/client/src/main/scala/org/scalaexercises/evaluator/EvaluatorClient.scala @@ -0,0 +1,34 @@ +/* + * scala-exercises-evaluator-client + * Copyright (C) 2015-2016 47 Degrees, LLC. + */ + +package org.scalaexercises.evaluator + +import cats.data.XorT +import cats.{MonadError, ~>} +import org.scalaexercises.evaluator.EvaluatorResponses.{EvaluationException, EvaluationResponse, EvaluationResult, EvalIO} +import org.scalaexercises.evaluator.free.algebra.EvaluatorOp + +class EvaluatorClient(url: String, authKey: String) { + + lazy val api = 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[M[_]](implicit I: (EvaluatorOp ~> M), + A: MonadError[M, Throwable]): M[EvaluationResponse[A]] = + evalIO foldMap I + + def liftEvaluator: XorT[EvalIO, EvaluationException, EvaluationResult[A]] = + XorT[EvalIO, EvaluationException, EvaluationResult[A]](evalIO) + + } +} diff --git a/client/src/main/scala/org/scalaexercises/evaluator/EvaluatorResponses.scala b/client/src/main/scala/org/scalaexercises/evaluator/EvaluatorResponses.scala index 0fd79bc1..b305a942 100644 --- a/client/src/main/scala/org/scalaexercises/evaluator/EvaluatorResponses.scala +++ b/client/src/main/scala/org/scalaexercises/evaluator/EvaluatorResponses.scala @@ -6,32 +6,36 @@ 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.language.higherKinds import scalaj.http.HttpResponse object EvaluatorResponses { - type EvaluationResponse[A] = EvalException Xor EvaluationResult[A] + type EvalIO[A] = Free[EvaluatorOp, A] + + type EvaluationResponse[A] = EvaluationException Xor EvaluationResult[A] case class EvaluationResult[A](result: A, statusCode: Int, headers: Map[String, IndexedSeq[String]]) - sealed abstract class EvalException(msg: String, - cause: Option[Throwable] = None) + sealed abstract class EvaluationException(msg: String, + cause: Option[Throwable] = None) extends Throwable(msg) { cause foreach initCause } case class JsonParsingException(msg: String, json: String) - extends EvalException(msg) + extends EvaluationException(msg) - case class UnexpectedException(msg: String) extends EvalException(msg) + case class UnexpectedException(msg: String) extends EvaluationException(msg) def toEntity[A](response: HttpResponse[String])( implicit D: Decoder[A]): EvaluationResponse[A] = response match { diff --git a/client/src/main/scala/org/scalaexercises/evaluator/api/Evaluator.scala b/client/src/main/scala/org/scalaexercises/evaluator/api/Evaluator.scala index e49dc5e8..c772d28f 100644 --- a/client/src/main/scala/org/scalaexercises/evaluator/api/Evaluator.scala +++ b/client/src/main/scala/org/scalaexercises/evaluator/api/Evaluator.scala @@ -6,7 +6,7 @@ package org.scalaexercises.evaluator.api import org.scalaexercises.evaluator.EvaluatorResponses.EvaluationResponse -import org.scalaexercises.evaluator.{Decoders, EvalRequest, EvalResponse} +import org.scalaexercises.evaluator.{Decoders, Dependency, EvalRequest, EvalResponse} import org.scalaexercises.evaluator.http.HttpClient import io.circe.generic.auto._ import io.circe.syntax._ @@ -19,8 +19,12 @@ class Evaluator { def eval(url: String, authKey: String, - evalRequest: EvalRequest): EvaluationResponse[EvalResponse] = - httpClient - .post[EvalResponse](url, authKey, data = evalRequest.asJson.noSpaces) + resolvers: List[String] = Nil, + dependencies: List[Dependency] = Nil, + code: String): EvaluationResponse[EvalResponse] = + httpClient.post[EvalResponse]( + url, + authKey, + data = EvalRequest(resolvers, dependencies, code).asJson.noSpaces) } diff --git a/client/src/main/scala/org/scalaexercises/evaluator/free/algebra/EvaluatorOps.scala b/client/src/main/scala/org/scalaexercises/evaluator/free/algebra/EvaluatorOps.scala new file mode 100644 index 00000000..11b3e555 --- /dev/null +++ b/client/src/main/scala/org/scalaexercises/evaluator/free/algebra/EvaluatorOps.scala @@ -0,0 +1,39 @@ +/* + * scala-exercises-evaluator-client + * Copyright (C) 2015-2016 47 Degrees, LLC. + */ + +package org.scalaexercises.evaluator.free.algebra + +import cats.free.{Free, Inject} +import org.scalaexercises.evaluator.{Dependency, EvalResponse} +import org.scalaexercises.evaluator.EvaluatorResponses.EvaluationResponse + +sealed trait EvaluatorOp[A] +final case class Evaluates(url: String, + authKey: String, + resolvers: List[String] = Nil, + dependencies: List[Dependency] = Nil, + code: String) + extends EvaluatorOp[EvaluationResponse[EvalResponse]] + +class EvaluatorOps[F[_]](implicit I: Inject[EvaluatorOp, F]) { + + def evaluates( + url: String, + authKey: String, + resolvers: List[String] = Nil, + dependencies: List[Dependency] = Nil, + code: String + ): Free[F, EvaluationResponse[EvalResponse]] = + Free.inject[EvaluatorOp, F]( + Evaluates(url, authKey, resolvers, dependencies, code)) + +} + +object EvaluatorOps { + + implicit def instance[F[_]]( + implicit I: Inject[EvaluatorOp, F]): EvaluatorOps[F] = new EvaluatorOps[F] + +} diff --git a/client/src/main/scala/org/scalaexercises/evaluator/free/interpreters/Interpreter.scala b/client/src/main/scala/org/scalaexercises/evaluator/free/interpreters/Interpreter.scala new file mode 100644 index 00000000..4d2ed871 --- /dev/null +++ b/client/src/main/scala/org/scalaexercises/evaluator/free/interpreters/Interpreter.scala @@ -0,0 +1,37 @@ +/* + * scala-exercises-evaluator-client + * Copyright (C) 2015-2016 47 Degrees, LLC. + */ + +package org.scalaexercises.evaluator.free.interpreters + +import cats.{ApplicativeError, Eval, MonadError, ~>} +import org.scalaexercises.evaluator.api.Evaluator +import org.scalaexercises.evaluator.free.algebra.{Evaluates, EvaluatorOp} + +import scala.language.higherKinds + +trait Interpreter { + + implicit def interpreter[M[_]]( + implicit A: MonadError[M, Throwable] + ): EvaluatorOp ~> M = evaluatorOpsInterpreter[M] + + /** + * Lifts Evaluator Ops to an effect capturing Monad such as Task via natural transformations + */ + def evaluatorOpsInterpreter[M[_]]( + implicit A: ApplicativeError[M, Throwable]): EvaluatorOp ~> M = + new (EvaluatorOp ~> M) { + + val evaluator = new Evaluator() + + def apply[A](fa: EvaluatorOp[A]): M[A] = fa match { + case Evaluates(url, authKey, resolvers, dependencies, code) ⇒ + A.pureEval( + Eval.later( + evaluator.eval(url, authKey, resolvers, dependencies, code))) + } + + } +} diff --git a/client/src/main/scala/org/scalaexercises/evaluator/implicits.scala b/client/src/main/scala/org/scalaexercises/evaluator/implicits.scala new file mode 100644 index 00000000..01290691 --- /dev/null +++ b/client/src/main/scala/org/scalaexercises/evaluator/implicits.scala @@ -0,0 +1,44 @@ +/* + * scala-exercises-evaluator-client + * Copyright (C) 2015-2016 47 Degrees, LLC. + */ + +package org.scalaexercises.evaluator + +import cats.std.FutureInstances +import cats.std.future._ +import cats.{Eval, MonadError} +import org.scalaexercises.evaluator.free.interpreters.Interpreter + +object implicits + extends Interpreter + with EvalInstances + with FutureInstances + +trait EvalInstances { + + implicit val evalMonadError: MonadError[Eval, Throwable] = + new MonadError[Eval, Throwable] { + + override def pure[A](x: A): Eval[A] = Eval.now(x) + + override def map[A, B](fa: Eval[A])(f: A ⇒ B): Eval[B] = fa.map(f) + + override def flatMap[A, B](fa: Eval[A])(ff: A ⇒ Eval[B]): Eval[B] = + fa.flatMap(ff) + + override def raiseError[A](e: Throwable): Eval[A] = + Eval.later({ throw e }) + + override def handleErrorWith[A](fa: Eval[A])( + f: Throwable ⇒ Eval[A]): Eval[A] = + Eval.later({ + try { + fa.value + } catch { + case e: Throwable ⇒ f(e).value + } + }) + } + +}