diff --git a/project/ProjectPlugin.scala b/project/ProjectPlugin.scala index a0247614..03490b18 100644 --- a/project/ProjectPlugin.scala +++ b/project/ProjectPlugin.scala @@ -16,8 +16,8 @@ object ProjectPlugin extends AutoPlugin { object V { lazy val cats = "2.7.0" - lazy val catsEffect = "2.5.4" - lazy val http4s = "0.21.31" + lazy val catsEffect = "3.3.4" + lazy val http4s = "0.23.8" lazy val circe = "0.14.1" lazy val log4s = "1.7.0" lazy val scalatest = "3.2.10" @@ -25,7 +25,7 @@ object ProjectPlugin extends AutoPlugin { lazy val jodaTime = "2.10.13" lazy val slf4j = "1.7.35" lazy val jwtCore = "9.0.3" - lazy val coursier = "2.0.16" + lazy val coursier = "2.1.0-M2-19-g5a34ba7c1" lazy val config = "1.4.1" lazy val scala = "2.13.8" } diff --git a/server/src/main/scala/org/scalaexercises/evaluator/auth.scala b/server/src/main/scala/org/scalaexercises/evaluator/auth.scala index cf3a81ef..e4182102 100644 --- a/server/src/main/scala/org/scalaexercises/evaluator/auth.scala +++ b/server/src/main/scala/org/scalaexercises/evaluator/auth.scala @@ -17,12 +17,14 @@ package org.scalaexercises.evaluator import cats.effect.Sync +import cats.syntax.list._ import com.typesafe.config._ import org.http4s._ import org.http4s.syntax.kleisli._ import org.http4s.util._ import org.log4s.getLogger import pdi.jwt.{Jwt, JwtAlgorithm} +import org.typelevel.ci.CIString import scala.util.{Failure, Success} @@ -30,6 +32,8 @@ object auth { private[this] val logger = getLogger + private[this] val tokenHeaderKey = CIString("X-Scala-Eval-Api-Token") + val config = ConfigFactory.load() val SecretKeyPath = "eval.auth.secretKey" @@ -46,34 +50,13 @@ object auth { def generateToken(value: String = "{}") = Jwt.encode(value, secretKey, JwtAlgorithm.HS256) - object `X-Scala-Eval-Api-Token` extends HeaderKey.Singleton { - - type HeaderT = `X-Scala-Eval-Api-Token` - - def name: CaseInsensitiveString = CaseInsensitiveString("x-scala-eval-api-token") - - override def parse(s: String): ParseResult[`X-Scala-Eval-Api-Token`] = - ParseResult.success(`X-Scala-Eval-Api-Token`(s)) - - def matchHeader(header: Header): Option[HeaderT] = - if (header.name == name) Some(`X-Scala-Eval-Api-Token`(header.value)) - else None - - } - - final case class `X-Scala-Eval-Api-Token`(token: String) extends Header.Parsed { - override def key = `X-Scala-Eval-Api-Token` - override def renderValue(writer: Writer): writer.type = - writer.append(token) - } - def apply[F[_]: Sync](service: HttpApp[F]): HttpApp[F] = HttpRoutes .of[F] { - case req if req.headers.nonEmpty => - req.headers.get(`X-Scala-Eval-Api-Token`) match { + case req if req.headers.headers.nonEmpty => + req.headers.get(tokenHeaderKey) match { case Some(header) => - Jwt.decodeRaw(header.token, secretKey, Seq(JwtAlgorithm.HS256)) match { + Jwt.decodeRaw(header.head.value, secretKey, Seq(JwtAlgorithm.HS256)) match { case Success(tokenIdentity) => logger.info(s"Auth success with identity : $tokenIdentity") service(req) diff --git a/server/src/main/scala/org/scalaexercises/evaluator/codecs.scala b/server/src/main/scala/org/scalaexercises/evaluator/codecs.scala deleted file mode 100644 index 40ffb51c..00000000 --- a/server/src/main/scala/org/scalaexercises/evaluator/codecs.scala +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2016-2020 47 Degrees Open Source - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.scalaexercises.evaluator - -import cats.effect.Sync -import io.circe.{Decoder, Encoder} -import org.http4s._ -import org.http4s.circe._ - -/** - * Provides Json serialization codecs for the http4s services - */ -trait Http4sCodecInstances { - - implicit def entityDecoderOf[F[_]: Sync, A: Decoder]: EntityDecoder[F, A] = jsonOf[F, A] - - implicit def entityEncoderOf[F[_]: Sync, A: Encoder]: EntityEncoder[F, A] = jsonEncoderOf[F, A] - -} - -object codecs extends Http4sCodecInstances diff --git a/server/src/main/scala/org/scalaexercises/evaluator/evaluation.scala b/server/src/main/scala/org/scalaexercises/evaluator/evaluation.scala index 88d5adee..6df1be8d 100644 --- a/server/src/main/scala/org/scalaexercises/evaluator/evaluation.scala +++ b/server/src/main/scala/org/scalaexercises/evaluator/evaluation.scala @@ -22,7 +22,6 @@ import java.security.MessageDigest import java.util.jar.JarFile import cats.effect._ -import cats.effect.syntax.concurrent.catsEffectSyntaxConcurrent import cats.implicits._ import coursier._ import coursier.cache.{ArtifactError, FileCache} @@ -39,10 +38,13 @@ import scala.tools.nsc.reporters._ import scala.tools.nsc.{Global, Settings} import scala.util.{Failure, Success, Try} import scala.util.control.NonFatal +import cats.effect.Temporal +import cats.effect.unsafe.IORuntime class Evaluator[F[_]: Sync](timeout: FiniteDuration = 20.seconds)(implicit - F: ConcurrentEffect[F], - T: Timer[F] + F: Async[F], + T: Temporal[F], + runtime: IORuntime ) { type Remote = String @@ -143,8 +145,11 @@ class Evaluator[F[_]: Sync](timeout: FiniteDuration = 20.seconds)(implicit allJars <- fetch(remotes, dependencies) result <- allJars match { case Right(jars) => - evaluate(code, jars) - .timeoutTo(timeout, Timeout[T](timeout).asInstanceOf[EvalResult[T]].pure[F]) + Clock[F].timeoutTo( + evaluate[T](code, jars), + timeout, + Timeout[T](timeout).asInstanceOf[EvalResult[T]].pure[F] + ) case Left(ex) => F.pure(UnresolvedDependency[T](ex.getMessage)) } } yield result @@ -201,7 +206,7 @@ private class StringCompiler( Nil) } - override def reset = { + override def reset() = { super.reset messages.clear() } @@ -296,7 +301,7 @@ private class StringCompiler( * - contruct an instance of that class * - return the result of `apply()` */ -class Eval(target: Option[File] = None, jars: List[File] = Nil) { +class Eval(target: Option[File] = None, jars: List[File] = Nil)(implicit runtime: IORuntime) { private lazy val compilerPath = try classPathOfClass("scala.tools.nsc.Interpreter") catch { diff --git a/server/src/main/scala/org/scalaexercises/evaluator/services.scala b/server/src/main/scala/org/scalaexercises/evaluator/services.scala index 8a67c58b..2df00a56 100644 --- a/server/src/main/scala/org/scalaexercises/evaluator/services.scala +++ b/server/src/main/scala/org/scalaexercises/evaluator/services.scala @@ -16,27 +16,29 @@ package org.scalaexercises.evaluator -import cats.effect.{ConcurrentEffect, ContextShift, ExitCode, IO, IOApp, Timer} +import cats.effect.{ExitCode, IO, IOApp} import cats.implicits._ -import coursier.interop.cats._ import coursier.util.Sync +import io.circe.Json import io.circe.generic.auto._ import io.circe.syntax._ import org.http4s._ +import org.http4s.circe.CirceEntityEncoder._ import org.http4s.dsl._ import org.http4s.headers.Allow import org.http4s.server.blaze._ import org.http4s.syntax.kleisli.http4sKleisliResponseSyntaxOptionT import org.log4s.getLogger -import org.scalaexercises.evaluator.codecs._ import scala.concurrent.duration._ +import cats.effect.Temporal +import org.http4s.circe.JsonDecoder object services { import EvalResponse.messages._ - val corsHeaders = Seq( + val corsHeaders: Seq[Header.ToRaw] = Seq( Header("Vary", "Origin,Access-Control-Request-Methods"), Header("Access-Control-Allow-Methods", "POST"), Header("Access-Control-Allow-Origin", "*"), @@ -44,7 +46,7 @@ object services { Header("Access-Control-Max-Age", 1.day.toSeconds.toString()) ) - def service[F[_]: ConcurrentEffect: ContextShift: Timer: Sync](evaluator: Evaluator[F]) = { + def service[F[_]: Temporal: Sync: JsonDecoder](evaluator: Evaluator[F]) = { object dsl extends Http4sDsl[F] diff --git a/server/src/main/scala/org/scalaexercises/evaluator/types.scala b/server/src/main/scala/org/scalaexercises/evaluator/types.scala index 884c99ff..b219ae44 100644 --- a/server/src/main/scala/org/scalaexercises/evaluator/types.scala +++ b/server/src/main/scala/org/scalaexercises/evaluator/types.scala @@ -16,7 +16,10 @@ package org.scalaexercises.evaluator +import io.circe.generic.semiauto.deriveDecoder + import scala.concurrent.duration._ +import io.circe.Decoder final case class RangePosition(start: Int, point: Int, end: Int) @@ -47,6 +50,11 @@ final case class CompilationError[A](compilationInfos: CI) extends EvalResult[A] final case class GeneralError[A](stack: Throwable) extends EvalResult[A] final case class Exclusion(organization: String, moduleName: String) + +object Exclusion { + implicit val decExclusion: Decoder[Exclusion] = deriveDecoder +} + final case class Dependency( groupId: String, artifactId: String, @@ -54,12 +62,20 @@ final case class Dependency( exclusions: Option[List[Exclusion]] = None ) +object Dependency { + implicit val decDependency: Decoder[Dependency] = deriveDecoder +} + final case class EvalRequest( resolvers: List[String] = Nil, dependencies: List[Dependency] = Nil, code: String ) +object EvalRequest { + implicit val decEvalRequest: Decoder[EvalRequest] = deriveDecoder +} + final case class EvalResponse( msg: String, value: Option[String] = None,