Skip to content

Commit b6d8437

Browse files
Merge pull request #15 from scala-exercises/jp-evaluator-sbt-multimodule
JP - Evaluator as SBT Multimodule
2 parents bbf9b8e + 8c4ea4b commit b6d8437

File tree

21 files changed

+763
-328
lines changed

21 files changed

+763
-328
lines changed

.scalafmt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
--style defaultWithAlign
2+
--maxColumn 80
3+
--continuationIndentCallSite 2
4+
--continuationIndentDefnSite 2
5+
--alignByOpenParenCallSite false

build.sbt

Lines changed: 44 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,51 @@
1-
val http4sVersion = "0.14.1"
1+
lazy val root = (project in file("."))
2+
.aggregate(`evaluator-server`, `evaluator-shared`, `evaluator-client`)
23

3-
val circeVersion = "0.4.1"
4+
lazy val `evaluator-shared` = (project in file("shared"))
5+
.settings(name := "evaluator-shared")
46

5-
lazy val evaluator = (project in file("."))
7+
lazy val `evaluator-client` = (project in file("client"))
8+
.dependsOn(`evaluator-shared`)
9+
.settings(
10+
name := "evaluator-client",
11+
libraryDependencies <++= libraryVersions { v => Seq(
12+
"org.typelevel" %% "cats-free" % v('cats),
13+
"io.circe" %% "circe-core" % v('circe),
14+
"io.circe" %% "circe-generic" % v('circe),
15+
"io.circe" %% "circe-parser" % v('circe),
16+
"org.log4s" %% "log4s" % v('log4s),
17+
"org.scalaj" %% "scalaj-http" % v('scalajhttp),
18+
"org.slf4j" % "slf4j-simple" % v('slf4j),
19+
// Testing libraries
20+
"org.scalatest" %% "scalatest" % v('scalaTest) % "test"
21+
)
22+
}
23+
)
24+
25+
lazy val `evaluator-server` = (project in file("server"))
26+
.dependsOn(`evaluator-shared`)
627
.enablePlugins(JavaAppPackaging)
728
.settings(
8-
name := "evaluator",
9-
scalaVersion := "2.11.8",
10-
resolvers += Resolver.sonatypeRepo("snapshots"),
11-
libraryDependencies ++= Seq(
12-
"org.scala-exercises" %% "evaluator-types" % version.value,
13-
"org.scala-lang" % "scala-compiler" % scalaVersion.value,
14-
"io.monix" %% "monix" % "2.0-RC8",
15-
"org.http4s" %% "http4s-dsl" % http4sVersion,
16-
"org.http4s" %% "http4s-blaze-server" % http4sVersion,
17-
"org.http4s" %% "http4s-blaze-client" % http4sVersion,
18-
"org.http4s" %% "http4s-circe" % http4sVersion,
19-
"io.circe" %% "circe-core" % circeVersion,
20-
"io.circe" %% "circe-generic" % circeVersion,
21-
"io.circe" %% "circe-parser" % circeVersion,
22-
"com.typesafe" % "config" % "1.3.0",
23-
"com.pauldijou" %% "jwt-core" % "0.8.0",
24-
"org.log4s" %% "log4s" % "1.3.0",
25-
"org.slf4j" % "slf4j-simple" % "1.7.21",
26-
"io.get-coursier" %% "coursier" % "1.0.0-M12",
27-
"io.get-coursier" %% "coursier-cache" % "1.0.0-M12",
28-
"org.scalatest" %% "scalatest" % "2.2.4" % "test"
29+
name := "evaluator-server",
30+
libraryDependencies <++= libraryVersions { v => Seq(
31+
"io.monix" %% "monix" % v('monix),
32+
"org.http4s" %% "http4s-dsl" % v('http4s),
33+
"org.http4s" %% "http4s-blaze-server" % v('http4s),
34+
"org.http4s" %% "http4s-blaze-client" % v('http4s),
35+
"org.http4s" %% "http4s-circe" % v('http4s),
36+
"io.circe" %% "circe-core" % v('circe),
37+
"io.circe" %% "circe-generic" % v('circe),
38+
"io.circe" %% "circe-parser" % v('circe),
39+
"com.typesafe" % "config" % v('config),
40+
"com.pauldijou" %% "jwt-core" % v('jwtcore),
41+
"org.log4s" %% "log4s" % v('log4s),
42+
"org.slf4j" % "slf4j-simple" % v('slf4j),
43+
"io.get-coursier" %% "coursier" % v('coursier),
44+
"io.get-coursier" %% "coursier-cache" % v('coursier),
45+
"org.scalatest" %% "scalatest" % v('scalaTest) % "test"
2946
)
47+
}
3048
)
49+
.settings(compilerDependencySettings: _*)
3150

32-
addCompilerPlugin(
33-
"org.scalamacros" % "paradise" % "2.1.0" cross CrossVersion.full
34-
)
35-
51+
onLoad in Global := (Command.process("project evaluator-server", _: State)) compose (onLoad in Global).value
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package org.scalaexercises.evaluator
2+
3+
import io.circe._, io.circe.jawn._, io.circe.syntax._
4+
5+
object Decoders {
6+
7+
implicit val decodeRangePosition: Decoder[RangePosition] =
8+
Decoder.forProduct3("start", "point", "end")(RangePosition.apply)
9+
10+
implicit val decodeCompilationInfo: Decoder[CompilationInfo] =
11+
Decoder.forProduct2("message", "pos")(CompilationInfo.apply)
12+
13+
implicit val decodeEvalResponse: Decoder[EvalResponse] =
14+
Decoder.forProduct4("msg", "value", "valueType", "compilationInfos")(
15+
EvalResponse.apply)
16+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package org.scalaexercises.evaluator
2+
3+
import cats.data.Xor
4+
import cats.syntax.xor._
5+
import io.circe.Decoder
6+
import io.circe.parser._
7+
import io.circe.generic.auto._
8+
9+
import scala.language.higherKinds
10+
import scalaj.http.HttpResponse
11+
12+
object EvaluatorResponses {
13+
14+
type EvaluationResponse[A] = EvalException Xor EvaluationResult[A]
15+
16+
case class EvaluationResult[A](result: A,
17+
statusCode: Int,
18+
headers: Map[String, IndexedSeq[String]])
19+
20+
sealed abstract class EvalException(msg: String,
21+
cause: Option[Throwable] = None)
22+
extends Throwable(msg) {
23+
cause foreach initCause
24+
}
25+
26+
case class JsonParsingException(msg: String, json: String)
27+
extends EvalException(msg)
28+
29+
case class UnexpectedException(msg: String) extends EvalException(msg)
30+
31+
def toEntity[A](response: HttpResponse[String])(
32+
implicit D: Decoder[A]): EvaluationResponse[A] = response match {
33+
case r if r.isSuccess
34+
decode[A](r.body).fold(
35+
e
36+
JsonParsingException(e.getMessage, r.body).left[EvaluationResult[A]],
37+
result
38+
Xor.Right(EvaluationResult(result, r.code, r.headers.toLowerCase))
39+
)
40+
case r
41+
UnexpectedException(
42+
s"Failed invoking get with status : ${r.code}, body : \n ${r.body}")
43+
.left[EvaluationResult[A]]
44+
}
45+
46+
implicit class HeadersLowerCase[A](headers: Map[String, A]) {
47+
48+
def toLowerCase: Map[String, A] = headers.map(e (e._1.toLowerCase, e._2))
49+
50+
}
51+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package org.scalaexercises.evaluator.api
2+
3+
import org.scalaexercises.evaluator.EvaluatorResponses.EvaluationResponse
4+
import org.scalaexercises.evaluator.{Decoders, EvalRequest, EvalResponse}
5+
import org.scalaexercises.evaluator.http.HttpClient
6+
import io.circe.generic.auto._
7+
import io.circe.syntax._
8+
9+
class Evaluator {
10+
11+
import Decoders._
12+
13+
private val httpClient = new HttpClient
14+
15+
def eval(url: String,
16+
authKey: String,
17+
evalRequest: EvalRequest): EvaluationResponse[EvalResponse] =
18+
httpClient
19+
.post[EvalResponse](url, authKey, data = evalRequest.asJson.noSpaces)
20+
21+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package org.scalaexercises.evaluator.http
2+
3+
import io.circe.Decoder
4+
import org.scalaexercises.evaluator.EvaluatorResponses
5+
import org.scalaexercises.evaluator.EvaluatorResponses.EvaluationResponse
6+
7+
object HttpClient {
8+
9+
val authHeaderName = "x-scala-eval-api-token"
10+
type Headers = Map[String, String]
11+
12+
}
13+
14+
class HttpClient {
15+
16+
import HttpClient._
17+
def post[A](
18+
url: String,
19+
secretKey: String,
20+
method: String = "post",
21+
headers: Headers = Map.empty,
22+
data: String
23+
)(implicit D: Decoder[A]): EvaluationResponse[A] =
24+
EvaluatorResponses.toEntity(
25+
HttpRequestBuilder(url = url, httpVerb = method)
26+
.withHeaders(headers + (authHeaderName -> secretKey))
27+
.withBody(data)
28+
.run)
29+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package org.scalaexercises.evaluator.http
2+
3+
import org.scalaexercises.evaluator.http.HttpClient._
4+
5+
import scalaj.http.Http
6+
7+
case class HttpRequestBuilder(
8+
url: String,
9+
httpVerb: String,
10+
headers: Headers = Map.empty[String, String],
11+
body: Option[String] = None
12+
) {
13+
14+
def withHeaders(headers: Headers) = copy(headers = headers)
15+
16+
def withBody(body: String) = copy(body = Option(body))
17+
18+
def run = {
19+
val request = Http(url).method(httpVerb).headers(headers)
20+
21+
body
22+
.fold(request)(
23+
request.postData(_).header("content-type", "application/json"))
24+
.asString
25+
}
26+
}

project/EvaluatorBuild.scala

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import org.scalafmt.sbt.ScalaFmtPlugin
2+
import org.scalafmt.sbt.ScalaFmtPlugin.autoImport._
3+
import sbt.Keys._
4+
import sbt._
5+
6+
object EvaluatorBuild extends AutoPlugin {
7+
8+
override def requires = plugins.JvmPlugin && ScalaFmtPlugin
9+
10+
override def trigger = allRequirements
11+
12+
object autoImport {
13+
14+
val libraryVersions = settingKey[Map[Symbol, String]]("Common versions to be used for dependencies")
15+
16+
def compilerDependencySettings = Seq(
17+
libraryDependencies ++= Seq(
18+
"org.scala-lang" % "scala-compiler" % scalaVersion.value,
19+
compilerPlugin(
20+
"org.scalamacros" % "paradise" % "2.1.0" cross CrossVersion.full
21+
)
22+
)
23+
)
24+
}
25+
26+
import autoImport._
27+
28+
override def projectSettings =
29+
baseSettings ++
30+
reformatOnCompileSettings ++
31+
dependencySettings ++
32+
miscSettings
33+
34+
35+
private[this] def baseSettings = Seq(
36+
version := "0.0.1-SNAPSHOT",
37+
organization := "org.scala-exercises",
38+
scalaVersion := "2.11.8",
39+
scalafmtConfig in ThisBuild := Some(file(".scalafmt")),
40+
41+
resolvers ++= Seq(Resolver.mavenLocal, Resolver.sonatypeRepo("snapshots"), Resolver.sonatypeRepo("releases")),
42+
43+
parallelExecution in Test := false,
44+
cancelable in Global := true,
45+
46+
scalacOptions ++= Seq(
47+
"-deprecation", "-feature", "-unchecked", "-encoding", "utf8"),
48+
scalacOptions ++= Seq(
49+
"-language:implicitConversions",
50+
"-language:higherKinds"),
51+
javacOptions ++= Seq("-encoding", "UTF-8", "-Xlint:-options")
52+
)
53+
54+
private[this] def dependencySettings = Seq(
55+
libraryVersions := Map(
56+
'cats -> "0.6.1",
57+
'circe -> "0.5.0-M2",
58+
'config -> "1.3.0",
59+
'coursier -> "1.0.0-M12",
60+
'http4s -> "0.14.1",
61+
'jwtcore -> "0.8.0",
62+
'log4s -> "1.3.0",
63+
'monix -> "2.0-RC8",
64+
'scalajhttp -> "2.3.0",
65+
'scalacheck -> "1.12.5",
66+
'scalaTest -> "2.2.6",
67+
'slf4j -> "1.7.21"
68+
)
69+
)
70+
71+
private[this] def miscSettings = Seq(
72+
shellPrompt := { s: State =>
73+
val c = scala.Console
74+
val blue = c.RESET + c.BLUE + c.BOLD
75+
val white = c.RESET + c.BOLD
76+
77+
val projectName = Project.extract(s).currentProject.id
78+
79+
s"$blue$projectName$white>${c.RESET}"
80+
}
81+
)
82+
}

project/build.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
sbt.version=0.13.9
1+
sbt.version=0.13.12

project/plugins.sbt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.1.1")
2+
addSbtPlugin("com.geirsson" % "sbt-scalafmt" % "0.2.11")

src/main/scala/auth.scala renamed to server/src/main/scala/auth.scala

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import scalaz.concurrent.Task
1212

1313
object auth {
1414

15-
private [this] val logger = getLogger
15+
private[this] val logger = getLogger
1616

1717
val config = ConfigFactory.load()
1818

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

27-
def generateToken(value : String = "{}") =
28+
def generateToken(value: String = "{}") =
2829
Jwt.encode(value, secretKey, JwtAlgorithm.HS256)
2930

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

4445
}
4546

46-
final case class `X-Scala-Eval-Api-Token`(token: String) extends Header.Parsed {
47+
final case class `X-Scala-Eval-Api-Token`(token: String)
48+
extends Header.Parsed {
4749
override def key = `X-Scala-Eval-Api-Token`
4850
override def renderValue(writer: Writer): writer.type =
4951
writer.append(token)

0 commit comments

Comments
 (0)