Skip to content

Add Cask tutorials #3056

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Oct 1, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Fixes
  • Loading branch information
gkepka committed Sep 8, 2024
commit 569d5df7a4135aa998d6e2a890488296f65c6247
40 changes: 20 additions & 20 deletions _overviews/toolkit/web-server-cookies-and-decorators.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,12 @@ To delete a cookie, set its `expires` parameter to an instant in the past, for e
import java.util.UUID
import java.util.concurrent.ConcurrentHashMap

object MyApp extends cask.MainRoutes {
object Example extends cask.MainRoutes {

val sessionIds = ConcurrentHashMap.newKeySet[String]()

@cask.get("/login")
def getLogin() = {
def getLogin(): cask.Response[String] = {
val html =
"""<!doctype html>
|<html>
Expand All @@ -55,7 +55,7 @@ object MyApp extends cask.MainRoutes {
}

@cask.postForm("/login")
def postLogin(name: String, password: String) = {
def postLogin(name: String, password: String): cask.Response[String] = {
if (name == "user" && password == "password") {
val sessionId = UUID.randomUUID().toString
sessionIds.add(sessionId)
Expand All @@ -66,7 +66,7 @@ object MyApp extends cask.MainRoutes {
}

@cask.get("/check")
def checkLogin(request: cask.Request) = {
def checkLogin(request: cask.Request): String = {
val sessionId = request.cookies.get("sessionId")
if (sessionId.exists(cookie => sessionIds.contains(cookie.value))) {
"You are logged in"
Expand All @@ -90,12 +90,12 @@ object MyApp extends cask.MainRoutes {
import java.util.UUID
import java.util.concurrent.ConcurrentHashMap

object MyApp extends cask.MainRoutes:
object Example extends cask.MainRoutes:

val sessionIds = ConcurrentHashMap.newKeySet[String]()

@cask.get("/login")
def getLogin() =
def getLogin(): cask.Response[String] =
val html =
"""<!doctype html>
|<html>
Expand All @@ -113,24 +113,24 @@ object MyApp extends cask.MainRoutes:
cask.Response(data = html, headers = Seq("Content-Type" -> "text/html"))

@cask.postForm("/login")
def postLogin(name: String, password: String) =
if name == "user" && password == "password":
def postLogin(name: String, password: String): cask.Response[String] =
if name == "user" && password == "password" then
val sessionId = UUID.randomUUID().toString
sessionIds.add(sessionId)
cask.Response(data = "Success!", cookies = Seq(cask.Cookie("sessionId", sessionId)))
else
cask.Response(data = "Authentication failed", statusCode = 401)

@cask.get("/check")
def checkLogin(request: cask.Request) =
val sessionId = request.cookies.get("sessionId")
if sessionId.exists(cookie => sessionIds.contains(cookie.value)):
"You are logged in"
else
"You are not logged in"
@cask.get("/check")
def checkLogin(request: cask.Request): String =
val sessionId = request.cookies.get("sessionId")
if sessionId.exists(cookie => sessionIds.contains(cookie.value)) then
"You are logged in"
else
"You are not logged in"

@cask.get("/logout")
def logout(sessionId: cask.Cookie) =
def logout(sessionId: cask.Cookie): cask.Response[String] =
sessionIds.remove(sessionId.value)
cask.Response(data = "Successfully logged out!", cookies = Seq(cask.Cookie("sessionId", "", expires = Instant.EPOCH)))

Expand All @@ -154,7 +154,7 @@ through the last argument group. Here we are passing the session identifier to a
{% tab 'Scala 2' %}
```scala
class loggedIn extends cask.RawDecorator {
override def wrapFunction(ctx: cask.Request, delegate: Delegate) = {
override def wrapFunction(ctx: cask.Request, delegate: Delegate): Result[Raw] = {
ctx.cookies.get("sessionId") match {
case Some(cookie) if sessionIds.contains(cookie.value) => delegate(Map("sessionId" -> cookie.value))
case _ => cask.router.Result.Success(cask.model.Response("You aren't logged in", 403))
Expand All @@ -164,15 +164,15 @@ class loggedIn extends cask.RawDecorator {

@loggedIn()
@cask.get("/decorated")
def decorated()(sessionId: String) = {
def decorated()(sessionId: String): String = {
s"You are logged in with id: $sessionId"
}
```
{% endtab %}
{% tab 'Scala 3' %}
```scala
class loggedIn extends cask.RawDecorator:
override def wrapFunction(ctx: cask.Request, delegate: Delegate) =
override def wrapFunction(ctx: cask.Request, delegate: Delegate): Result[Raw] =
ctx.cookies.get("sessionId") match
case Some(cookie) if sessionIds.contains(cookie.value) =>
delegate(Map("sessionId" -> cookie.value))
Expand All @@ -182,7 +182,7 @@ class loggedIn extends cask.RawDecorator:

@loggedIn()
@cask.get("/decorated")
def decorated()(sessionId: String) = s"You are logged in with id: $sessionId"
def decorated()(sessionId: String): String = s"You are logged in with id: $sessionId"
```
{% endtab %}
{% endtabs %}
16 changes: 8 additions & 8 deletions _overviews/toolkit/web-server-dynamic.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ You can create an endpoint returning dynamically generated content with `@cask.g
```scala
import java.time.ZonedDateTime

object MyApp extends cask.MainRoutes {
object Example extends cask.MainRoutes {
@cask.get("/time")
def dynamic(): String = s"Current date is: ${ZonedDateTime.now()}"

Expand All @@ -30,7 +30,7 @@ object MyApp extends cask.MainRoutes {
```scala
import java.time.ZonedDateTime

object MyApp extends cask.MainRoutes:
object Example extends cask.MainRoutes:
@cask.get("/time")
def dynamic(): String = s"Current date is: ${ZonedDateTime.now()}"

Expand Down Expand Up @@ -58,9 +58,9 @@ scala-cli run Example.scala
```
{% endtab %}
{% tab 'sbt' %}
In the sbt shell, the following command will start the server:
In the terminal, the following command will start the server:
```
sbt:example> example/run
sbt example/run
```
{% endtab %}
{% tab 'Mill' %}
Expand Down Expand Up @@ -88,7 +88,7 @@ in a given city.
```scala
import java.time.{ZoneId, ZonedDateTime}

object MyApp extends cask.MainRoutes {
object Example extends cask.MainRoutes {

private def getZoneIdForCity(city: String): Option[ZoneId] = {
import scala.jdk.CollectionConverters._
Expand All @@ -111,7 +111,7 @@ object MyApp extends cask.MainRoutes {
```scala
import java.time.{ZoneId, ZonedDateTime}

object MyApp extends cask.MainRoutes:
object Example extends cask.MainRoutes:

private def getZoneIdForCity(city: String): Option[ZoneId] =
import scala.jdk.CollectionConverters.*
Expand Down Expand Up @@ -177,7 +177,7 @@ setting the `Content-Type` header to `text/html`.
import java.time.{ZoneId, ZonedDateTime}
import scalatags.Text.all._

object MyApp extends cask.MainRoutes {
object Example extends cask.MainRoutes {

private def getZoneIdForCity(city: String): Option[ZoneId] = {
import scala.jdk.CollectionConverters._
Expand Down Expand Up @@ -209,7 +209,7 @@ object MyApp extends cask.MainRoutes {
import java.time.{ZoneId, ZonedDateTime}
import scalatags.Text.all.*

object MyApp extends cask.MainRoutes:
object Example extends cask.MainRoutes:

private def getZoneIdForCity(city: String): Option[ZoneId] =
import scala.jdk.CollectionConverters.*
Expand Down
38 changes: 22 additions & 16 deletions _overviews/toolkit/web-server-input.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ with names corresponding to names of fields in the form and set the form method
{% tabs web-server-input-1 class=tabs-scala-version %}
{% tab 'Scala 2' %}
```scala
object MyApp extends cask.MainRoutes {
object Example extends cask.MainRoutes {

@cask.get("/form")
def getForm(): cask.Response = {
def getForm(): cask.Response[String] = {
val html =
"""<!doctype html>
|<html>
Expand Down Expand Up @@ -48,10 +48,10 @@ object MyApp extends cask.MainRoutes {
{% endtab %}
{% tab 'Scala 3' %}
```scala
object MyApp extends cask.MainRoutes:
object Example extends cask.MainRoutes:

@cask.get("/form")
def getForm(): cask.Response =
def getForm(): cask.Response[String] =
val html =
"""<!doctype html>
|<html>
Expand All @@ -78,8 +78,8 @@ object MyApp extends cask.MainRoutes:
{% endtabs %}

In this example we create a form asking for name and surname of a user and then redirect the user to a greeting page. Notice the
use of `cask.Response`. The default returned content type in case of `String` returning endpoint method is `text/plain`,
set it to `text/html` in order for browser to display the form correctly.
use of `cask.Response`. The `cask.Response` type allows user to set the status code, headers and cookies. The default
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
use of `cask.Response`. The `cask.Response` type allows user to set the status code, headers and cookies. The default
use of `cask.Response`. The `cask.Response` type allows the user to set the status code, headers and cookies. The default
content type in case of `String` returning endpoint method is `text/plain`, set it to `text/html` in order for browser to display the form correctly.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
content type in case of `String` returning endpoint method is `text/plain`, set it to `text/html` in order for browser to display the form correctly.
content type for an endpoint method returning a `String` is `text/plain`. Set it to `text/html` in order for the browser to display the form correctly.

The `formEndpoint` endpoint reads the form data using `name` and `surname` parameters. The names of parameters must
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
The `formEndpoint` endpoint reads the form data using `name` and `surname` parameters. The names of parameters must
The `formEndpoint` endpoint reads the form data using the `name` and `surname` parameters. The names of parameters must
be identical to the field names of the form.
Expand All @@ -92,7 +92,7 @@ will be read into the endpoint method arguments.
{% tabs web-server-input-2 class=tabs-scala-version %}
{% tab 'Scala 2' %}
```scala
object MyApp extends cask.MainRoutes {
object Example extends cask.MainRoutes {

@cask.postJson("/json")
def jsonEndpoint(name: String, surname: String): String =
Expand All @@ -104,9 +104,9 @@ object MyApp extends cask.MainRoutes {
{% endtab %}
{% tab 'Scala 3' %}
```scala
object MyApp extends cask.MainRoutes:
object Example extends cask.MainRoutes:

@cask.postJson("/json")
@cask.postJson("/json")
def jsonEndpoint(name: String, surname: String): String =
"Hello " + name + " " + surname

Expand Down Expand Up @@ -138,7 +138,7 @@ from uPickle library.
{% tabs web-server-input-3 class=tabs-scala-version %}
{% tab 'Scala 2' %}
```scala
object MyApp extends cask.MainRoutes {
object Example extends cask.MainRoutes {

@cask.postJson("/json")
def jsonEndpoint(value: ujson.Value): String =
Expand All @@ -151,7 +151,7 @@ object MyApp extends cask.MainRoutes {
{% endtab %}
{% tab 'Scala 3' %}
```scala
object MyApp extends cask.MainRoutes:
object Example extends cask.MainRoutes:

@cask.postJson("/json")
def jsonEndpoint(value: ujson.Value): String =
Expand Down Expand Up @@ -189,7 +189,9 @@ location. To serialize a case class into JSON, use type class derivation or defi
{% tabs web-server-input-4 class=tabs-scala-version %}
{% tab 'Scala 2' %}
```scala
object MyApp extends cask.MainRoutes {
import java.time.{ZoneId, ZonedDateTime}

object Example extends cask.MainRoutes {
import upickle.default.{ReadWriter, macroRW, writeJs}
case class TimeData(timezone: Option[String], time: String)
object TimeData {
Expand All @@ -210,12 +212,16 @@ object MyApp extends cask.MainRoutes {
}
writeJs(TimeData(timezone.map(_.toString), time))
}

initialize()
}
```
{% endtab %}
{% tab 'Scala 3' %}
```scala
object MyApp extends cask.MainRoutes {
import java.time.{ZoneId, ZonedDateTime}

object Example extends cask.MainRoutes:
import upickle.default.{ReadWriter, writeJs}
case class TimeData(timezone: Option[String], time: String) derives ReadWriter

Expand All @@ -224,14 +230,14 @@ object MyApp extends cask.MainRoutes {
ZoneId.getAvailableZoneIds.asScala.find(_.endsWith("/" + city)).map(ZoneId.of)

@cask.get("/time_json/:city")
def timeJSON(city: String): ujson.Value = {
def timeJSON(city: String): ujson.Value =
val timezone = getZoneIdForCity(city)
val time = timezone match
case Some(zoneId)=> s"Current date is: ${ZonedDateTime.now().withZoneSameInstant(zoneId)}"
case None => s"Couldn't find time zone for city $city"
writeJs(TimeData(timezone.map(_.toString), time))
}
}

initialize()
```
{% endtab %}
{% endtabs %}
18 changes: 9 additions & 9 deletions _overviews/toolkit/web-server-query-parameters.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ In this example, the `city` parameter will be optional, which you specify in Cas
```scala
import java.time.{ZoneId, ZonedDateTime}

object MyApp extends cask.MainRoutes {
object Example extends cask.MainRoutes {

private def getZoneIdForCity(city: String): Option[ZoneId] = {
import scala.jdk.CollectionConverters._
Expand All @@ -48,19 +48,19 @@ object MyApp extends cask.MainRoutes {
```scala
import java.time.{ZoneId, ZonedDateTime}

object MyApp extends cask.MainRoutes:
object Example extends cask.MainRoutes:

private def getZoneIdForCity(city: String): Option[ZoneId] =
import scala.jdk.CollectionConverters.*
ZoneId.getAvailableZoneIds.asScala.find(_.endsWith("/" + city)).map(ZoneId.of)

@cask.get("/time")
def dynamicWithParam(city: Option[String] = None): String =
city match
case Some(value) => getZoneIdForCity(value) match
case Some(zoneId) => s"Current date is: ${ZonedDateTime.now().withZoneSameInstant(zoneId)}"
case None => s"Couldn't find time zone for city $value"
case None => s"Current date is: ${ZonedDateTime.now()}"
@cask.get("/time")
def dynamicWithParam(city: Option[String] = None): String =
city match
case Some(value) => getZoneIdForCity(value) match
case Some(zoneId) => s"Current date is: ${ZonedDateTime.now().withZoneSameInstant(zoneId)}"
case None => s"Couldn't find time zone for city $value"
case None => s"Current date is: ${ZonedDateTime.now()}"

initialize()
```
Expand Down
4 changes: 2 additions & 2 deletions _overviews/toolkit/web-server-static.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,9 +143,9 @@ scala-cli run Example.scala
```
{% endtab %}
{% tab 'sbt' %}
In the sbt shell, the following command will start the server:
In the terminal, the following command will start the server:
```
sbt:example> example/run
sbt example/run
```
{% endtab %}
{% tab 'Mill' %}
Expand Down
7 changes: 6 additions & 1 deletion _overviews/toolkit/web-server-websockets.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ private def getZoneIdForCity(city: String): Option[ZoneId] = {
}

@cask.websocket("/websocket")
def websocket(): cask.WsHandler =
def websocket(): cask.WsHandler = {
cask.WsHandler { channel =>
cask.WsActor {
case cask.Ws.Text("") => channel.send(cask.Ws.Close())
Expand All @@ -81,6 +81,9 @@ def websocket(): cask.WsHandler =
channel.send(cask.Ws.Text(text))
}
}
}

initialize()
```
{% endtab %}
{% tab 'Scala 3' %}
Expand All @@ -104,6 +107,8 @@ def websocket(): cask.WsHandler =
channel.send(cask.Ws.Text(text))
}
}

initialize()
```
{% endtab %}
{% endtabs %}
Expand Down