Skip to content

Commit 270ac50

Browse files
authored
Merge pull request #11 from scala-exercises/rr-document-endpoint
Document Eval Endpoint
2 parents 683f1fd + e00d9ef commit 270ac50

File tree

3 files changed

+167
-4
lines changed

3 files changed

+167
-4
lines changed

README.md

Lines changed: 153 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,153 @@
1-
# evaluator
2-
A remote Scala code evaluator
1+
# Remote Scala Eval
2+
3+
The remote Scala evaluator is a server based application that
4+
allows remote evaluation of arbitrary Scala code.
5+
6+
# Run from sources
7+
8+
```bash
9+
sbt run
10+
```
11+
12+
# Authentication
13+
14+
The remote Scala eval uses [JWT](https://jwt.io/) to encode / decode tokens.
15+
The `secretKey` used for encoding/decoding is configurable as part of the service configuration in
16+
`src/main/resources/application.conf`.
17+
18+
Please change `secretKey` by overriding it or providing the `EVAL_SECRET_KEY` env var.
19+
20+
```
21+
eval.auth {
22+
secretKey = "secretKey"
23+
secretKey = ${?EVAL_SECRET_KEY}
24+
}
25+
```
26+
27+
## Generate an auth token
28+
29+
In order to generate an auth token you may use the scala console and invoke
30+
the `org.scalaexercises.evaluator.auth#generateToken` like so
31+
32+
```
33+
sbt console
34+
35+
scala> import org.scalaexercises.evaluator.auth._
36+
37+
scala> generateToken("your identity")
38+
res0: String = eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eW91ciBpZGVudGl0eQ.cfH43Wa7k_w1i0W2pQhV1k21t2JqER9lw5EpJcENRMI
39+
```
40+
41+
Note `your identity` is exclusively to identify incoming requests for logging purposes.
42+
The Scala evaluator will authorize any incoming request generated with the `secretKey`
43+
44+
# Request
45+
46+
Requests are sent in JSON format via HTTP POST and are authenticated via the `x-scala-eval-api-token` header.
47+
48+
# Sample Request
49+
50+
Given the token above a sample request may look like:
51+
52+
```bash
53+
curl -X POST -H "Content-Type: application/json" -H "x-scala-eval-api-token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eW91ciBpZGVudGl0eQ.cfH43Wa7k_w1i0W2pQhV1k21t2JqER9lw5EpJcENRMI" -d '{
54+
"resolvers":[
55+
"https://oss.sonatype.org/content/repositories/releases"
56+
],
57+
"dependencies":[
58+
{
59+
"groupId":"org.typelevel",
60+
"artifactId":"cats-core_2.11",
61+
"version":"0.4.1"
62+
}
63+
],
64+
"code":"{import cats._; Monad[Id].pure(42)}"
65+
}
66+
' "http://localhost:8080/eval"
67+
```
68+
69+
## Headers
70+
71+
```
72+
Content-Type : application/json
73+
x-scala-eval-api-token : eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eW91ciBpZGVudGl0eQ.cfH43Wa7k_w1i0W2pQhV1k21t2JqER9lw5EpJcENRMI
74+
```
75+
76+
## Body
77+
78+
```json
79+
{
80+
"resolvers":[
81+
"https://oss.sonatype.org/content/repositories/releases"
82+
],
83+
"dependencies":[
84+
{
85+
"groupId":"org.typelevel",
86+
"artifactId":"cats-core_2.11",
87+
"version":"0.4.1"
88+
}
89+
],
90+
"code":"{import cats._; Monad[Id].pure(42)}"
91+
}
92+
```
93+
94+
- `resolvers` : A list of resolvers where artifacts dependencies are hosted
95+
- `dependencies` : A List of artifacts required to eval the code
96+
- `code` : Some Scala Code
97+
98+
## Response
99+
100+
After compiling and attempting to evaluate the server will return a response payload with the following fields:
101+
102+
- `msg` : A message indicating the result of the compilation and evaluation. Note failing to compile still yields http status 200 as the purpose of the service is to output a result without judging if the input was correct or not.
103+
- `value` : The result of the evaluation or `null` if it didn't compile or an exception is thrown.
104+
- `valueType` : The type of result or `null` if it didn't compile or an exception is thrown
105+
- `compilationInfos` : A map of compilation severity errors and their associated message
106+
107+
For ilustration purposes here is a few Response Payload examples:
108+
109+
Successful compilation and Evaluation
110+
111+
```json
112+
{
113+
"msg": "Ok",
114+
"value": "42",
115+
"valueType": "java.lang.Integer",
116+
"compilationInfos": {}
117+
}
118+
```
119+
120+
Compilation Failure
121+
122+
```json
123+
{
124+
"msg": "Compilation Error",
125+
"value": null,
126+
"valueType": null,
127+
"compilationInfos": {
128+
"ERROR": [
129+
{
130+
"message": "value x is not a member of cats.Monad[cats.Id]",
131+
"pos": {
132+
"start": 165,
133+
"point": 165,
134+
"end": 165
135+
}
136+
}
137+
]
138+
}
139+
}
140+
```
141+
142+
Evaluating code that may result in a thrown exception
143+
144+
```json
145+
{
146+
"msg": "Runtime Error",
147+
"value": null,
148+
"valueType": null,
149+
"compilationInfos": {}
150+
}
151+
```
152+
153+

build.sbt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,4 @@ enablePlugins(JavaAppPackaging)
3232
addCompilerPlugin(
3333
"org.scalamacros" % "paradise" % "2.1.0" cross CrossVersion.full
3434
)
35+

src/main/scala/auth.scala

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,14 @@ import org.http4s.util._
66
import scala.util.{Try, Success, Failure}
77
import pdi.jwt.{Jwt, JwtAlgorithm, JwtHeader, JwtClaim, JwtOptions}
88

9+
import org.log4s.getLogger
10+
911
import scalaz.concurrent.Task
1012

1113
object auth {
1214

15+
private [this] val logger = getLogger
16+
1317
val config = ConfigFactory.load()
1418

1519
val SecretKeyPath = "eval.auth.secretKey"
@@ -20,6 +24,9 @@ object auth {
2024
throw new IllegalStateException("Missing -Deval.auth.secretKey=[YOUR_KEY_HERE] or env var [EVAL_SECRET_KEY] ")
2125
}
2226

27+
def generateToken(value : String = "{}") =
28+
Jwt.encode(value, secretKey, JwtAlgorithm.HS256)
29+
2330
object `X-Scala-Eval-Api-Token` extends HeaderKey.Singleton {
2431

2532
type HeaderT = `X-Scala-Eval-Api-Token`
@@ -46,8 +53,12 @@ object auth {
4653
req.headers.get(`X-Scala-Eval-Api-Token`) match {
4754
case Some(header) =>
4855
Jwt.decodeRaw(header.value, secretKey, Seq(JwtAlgorithm.HS256)) match {
49-
case Success(_) => service(req)
50-
case Failure(_) => Task.now(Response(Status.Unauthorized))
56+
case Success(tokenIdentity) =>
57+
logger.info(s"Auth success with identity : $tokenIdentity")
58+
service(req)
59+
case Failure(ex) =>
60+
logger.warn(s"Auth failed : $ex")
61+
Task.now(Response(Status.Unauthorized))
5162
}
5263
case None => Task.now(Response(Status.Unauthorized))
5364
}

0 commit comments

Comments
 (0)