Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .scalafmt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
--style defaultWithAlign
--maxColumn 80
--continuationIndentCallSite 2
--continuationIndentDefnSite 2
--alignByOpenParenCallSite false
72 changes: 44 additions & 28 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -1,35 +1,51 @@
val http4sVersion = "0.14.1"
lazy val root = (project in file("."))
.aggregate(`evaluator-server`, `evaluator-shared`, `evaluator-client`)

val circeVersion = "0.4.1"
lazy val `evaluator-shared` = (project in file("shared"))
.settings(name := "evaluator-shared")

lazy val evaluator = (project in file("."))
lazy val `evaluator-client` = (project in file("client"))
.dependsOn(`evaluator-shared`)
.settings(
name := "evaluator-client",
libraryDependencies <++= libraryVersions { v => Seq(
"org.typelevel" %% "cats-free" % v('cats),
"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"
)
}
)

lazy val `evaluator-server` = (project in file("server"))
.dependsOn(`evaluator-shared`)
.enablePlugins(JavaAppPackaging)
.settings(
name := "evaluator",
scalaVersion := "2.11.8",
resolvers += Resolver.sonatypeRepo("snapshots"),
libraryDependencies ++= Seq(
"org.scala-exercises" %% "evaluator-types" % version.value,
"org.scala-lang" % "scala-compiler" % scalaVersion.value,
"io.monix" %% "monix" % "2.0-RC8",
"org.http4s" %% "http4s-dsl" % http4sVersion,
"org.http4s" %% "http4s-blaze-server" % http4sVersion,
"org.http4s" %% "http4s-blaze-client" % http4sVersion,
"org.http4s" %% "http4s-circe" % http4sVersion,
"io.circe" %% "circe-core" % circeVersion,
"io.circe" %% "circe-generic" % circeVersion,
"io.circe" %% "circe-parser" % circeVersion,
"com.typesafe" % "config" % "1.3.0",
"com.pauldijou" %% "jwt-core" % "0.8.0",
"org.log4s" %% "log4s" % "1.3.0",
"org.slf4j" % "slf4j-simple" % "1.7.21",
"io.get-coursier" %% "coursier" % "1.0.0-M12",
"io.get-coursier" %% "coursier-cache" % "1.0.0-M12",
"org.scalatest" %% "scalatest" % "2.2.4" % "test"
name := "evaluator-server",
libraryDependencies <++= libraryVersions { v => Seq(
"io.monix" %% "monix" % v('monix),
"org.http4s" %% "http4s-dsl" % v('http4s),
"org.http4s" %% "http4s-blaze-server" % v('http4s),
"org.http4s" %% "http4s-blaze-client" % v('http4s),
"org.http4s" %% "http4s-circe" % v('http4s),
"io.circe" %% "circe-core" % v('circe),
"io.circe" %% "circe-generic" % v('circe),
"io.circe" %% "circe-parser" % v('circe),
"com.typesafe" % "config" % v('config),
"com.pauldijou" %% "jwt-core" % v('jwtcore),
"org.log4s" %% "log4s" % v('log4s),
"org.slf4j" % "slf4j-simple" % v('slf4j),
"io.get-coursier" %% "coursier" % v('coursier),
"io.get-coursier" %% "coursier-cache" % v('coursier),
"org.scalatest" %% "scalatest" % v('scalaTest) % "test"
)
}
)
.settings(compilerDependencySettings: _*)

addCompilerPlugin(
"org.scalamacros" % "paradise" % "2.1.0" cross CrossVersion.full
)

onLoad in Global := (Command.process("project evaluator-server", _: State)) compose (onLoad in Global).value
16 changes: 16 additions & 0 deletions client/src/main/scala/org/scalaexercises/evaluator/Decoders.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.scalaexercises.evaluator

import io.circe._, io.circe.jawn._, io.circe.syntax._

object Decoders {

implicit val decodeRangePosition: Decoder[RangePosition] =
Decoder.forProduct3("start", "point", "end")(RangePosition.apply)

implicit val decodeCompilationInfo: Decoder[CompilationInfo] =
Decoder.forProduct2("message", "pos")(CompilationInfo.apply)

implicit val decodeEvalResponse: Decoder[EvalResponse] =
Decoder.forProduct4("msg", "value", "valueType", "compilationInfos")(
EvalResponse.apply)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package org.scalaexercises.evaluator

import cats.data.Xor
import cats.syntax.xor._
import io.circe.Decoder
import io.circe.parser._
import io.circe.generic.auto._

import scala.language.higherKinds
import scalaj.http.HttpResponse

object EvaluatorResponses {

type EvaluationResponse[A] = EvalException 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)
extends Throwable(msg) {
cause foreach initCause
}

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

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

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

implicit class HeadersLowerCase[A](headers: Map[String, A]) {

def toLowerCase: Map[String, A] = headers.map(e ⇒ (e._1.toLowerCase, e._2))

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package org.scalaexercises.evaluator.api

import org.scalaexercises.evaluator.EvaluatorResponses.EvaluationResponse
import org.scalaexercises.evaluator.{Decoders, EvalRequest, EvalResponse}
import org.scalaexercises.evaluator.http.HttpClient
import io.circe.generic.auto._
import io.circe.syntax._

class Evaluator {

import Decoders._

private val httpClient = new HttpClient

def eval(url: String,
authKey: String,
evalRequest: EvalRequest): EvaluationResponse[EvalResponse] =
httpClient
.post[EvalResponse](url, authKey, data = evalRequest.asJson.noSpaces)

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package org.scalaexercises.evaluator.http

import io.circe.Decoder
import org.scalaexercises.evaluator.EvaluatorResponses
import org.scalaexercises.evaluator.EvaluatorResponses.EvaluationResponse

object HttpClient {

val authHeaderName = "x-scala-eval-api-token"
type Headers = Map[String, String]

}

class HttpClient {

import HttpClient._
def post[A](
url: String,
secretKey: String,
method: String = "post",
headers: Headers = Map.empty,
data: String
)(implicit D: Decoder[A]): EvaluationResponse[A] =
EvaluatorResponses.toEntity(
HttpRequestBuilder(url = url, httpVerb = method)
.withHeaders(headers + (authHeaderName -> secretKey))
.withBody(data)
.run)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package org.scalaexercises.evaluator.http

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

import scalaj.http.Http

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

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

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

def run = {
val request = Http(url).method(httpVerb).headers(headers)

body
.fold(request)(
request.postData(_).header("content-type", "application/json"))
.asString
}
}
82 changes: 82 additions & 0 deletions project/EvaluatorBuild.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import org.scalafmt.sbt.ScalaFmtPlugin
import org.scalafmt.sbt.ScalaFmtPlugin.autoImport._
import sbt.Keys._
import sbt._

object EvaluatorBuild extends AutoPlugin {

override def requires = plugins.JvmPlugin && ScalaFmtPlugin

override def trigger = allRequirements

object autoImport {

val libraryVersions = settingKey[Map[Symbol, String]]("Common versions to be used for dependencies")

def compilerDependencySettings = Seq(
libraryDependencies ++= Seq(
"org.scala-lang" % "scala-compiler" % scalaVersion.value,
compilerPlugin(
"org.scalamacros" % "paradise" % "2.1.0" cross CrossVersion.full
)
)
)
}

import autoImport._

override def projectSettings =
baseSettings ++
reformatOnCompileSettings ++
dependencySettings ++
miscSettings


private[this] def baseSettings = Seq(
version := "0.0.1-SNAPSHOT",
organization := "org.scala-exercises",
scalaVersion := "2.11.8",
scalafmtConfig in ThisBuild := Some(file(".scalafmt")),

resolvers ++= Seq(Resolver.mavenLocal, Resolver.sonatypeRepo("snapshots"), Resolver.sonatypeRepo("releases")),

parallelExecution in Test := false,
cancelable in Global := true,

scalacOptions ++= Seq(
"-deprecation", "-feature", "-unchecked", "-encoding", "utf8"),
scalacOptions ++= Seq(
"-language:implicitConversions",
"-language:higherKinds"),
javacOptions ++= Seq("-encoding", "UTF-8", "-Xlint:-options")
)

private[this] def dependencySettings = Seq(
libraryVersions := Map(
'cats -> "0.6.1",
'circe -> "0.5.0-M2",
'config -> "1.3.0",
'coursier -> "1.0.0-M12",
'http4s -> "0.14.1",
'jwtcore -> "0.8.0",
'log4s -> "1.3.0",
'monix -> "2.0-RC8",
'scalajhttp -> "2.3.0",
'scalacheck -> "1.12.5",
'scalaTest -> "2.2.6",
'slf4j -> "1.7.21"
)
)

private[this] def miscSettings = Seq(
shellPrompt := { s: State =>
val c = scala.Console
val blue = c.RESET + c.BLUE + c.BOLD
val white = c.RESET + c.BOLD

val projectName = Project.extract(s).currentProject.id

s"$blue$projectName$white>${c.RESET}"
}
)
}
2 changes: 1 addition & 1 deletion project/build.properties
Original file line number Diff line number Diff line change
@@ -1 +1 @@
sbt.version=0.13.9
sbt.version=0.13.12
1 change: 1 addition & 0 deletions project/plugins.sbt
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.1.1")
addSbtPlugin("com.geirsson" % "sbt-scalafmt" % "0.2.11")
10 changes: 6 additions & 4 deletions src/main/scala/auth.scala → server/src/main/scala/auth.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import scalaz.concurrent.Task

object auth {

private [this] val logger = getLogger
private[this] val logger = getLogger

val config = ConfigFactory.load()

Expand All @@ -21,10 +21,11 @@ object auth {
val secretKey = if (config.hasPath(SecretKeyPath)) {
config.getString(SecretKeyPath)
} else {
throw new IllegalStateException("Missing -Deval.auth.secretKey=[YOUR_KEY_HERE] or env var [EVAL_SECRET_KEY] ")
throw new IllegalStateException(
"Missing -Deval.auth.secretKey=[YOUR_KEY_HERE] or env var [EVAL_SECRET_KEY] ")
}

def generateToken(value : String = "{}") =
def generateToken(value: String = "{}") =
Jwt.encode(value, secretKey, JwtAlgorithm.HS256)

object `X-Scala-Eval-Api-Token` extends HeaderKey.Singleton {
Expand All @@ -43,7 +44,8 @@ object auth {

}

final case class `X-Scala-Eval-Api-Token`(token: String) extends Header.Parsed {
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)
Expand Down
Loading