Skip to content

Instantly share code, notes, and snippets.

@shishkin
Last active August 29, 2015 14:07
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save shishkin/9ff6be6f424218f10f4b to your computer and use it in GitHub Desktop.
Save shishkin/9ff6be6f424218f10f4b to your computer and use it in GitHub Desktop.
Demo code from the Singapore Scala meet-up: http://www.meetup.com/Singapore-Scala-Programmers/events/207505482/
package demo
object ScalaDsl {
/*
* Defining a simplistic model for the web app DSL
*/
case class HttpRequest(path: String, headers: Map[String, String], body: Option[String])
case class HttpResponse(status: Int, headers: Map[String, String], body: Option[String])
type Route = PartialFunction[HttpRequest, HttpResponse]
/*
* Sidenote on Option[T]
*/
val opt: Option[Int] = Some(3)
//> opt : Option[Int] = Some(3)
val opt2: Option[Int] = None
//> opt2 : Option[Int] = None
val i = opt match {
case Some(x) => x
case None => 0
}
//> i : Int = 3
opt map (_.toString)
//> res0: Option[String] = Some(3)
opt2 map (_.toString)
//> res1: Option[String] = None
/*
* Defining and using a route
*/
val hello: Route = {
case HttpRequest("/hello", _, Some(name)) =>
HttpResponse(200, Map(), Some(s"Hello $name!"))
}
//> hello : demo.ScalaDsl.Route = <function1>
val notFound: Route = {
case _ => HttpResponse(404, Map(), Some("Not Found"))
}
//> notFound : demo.ScalaDsl.Route = <function1>
val server = hello orElse notFound
//> server : PartialFunction[demo.ScalaDsl.HttpRequest,demo.ScalaDsl.HttpResponse] = <function1>
server(HttpRequest("/hello", Map(), Some("Scala")))
//> res2: demo.ScalaDsl.HttpResponse = HttpResponse(200,Map(),Some(Hello Scala!))
server(HttpRequest("/foo", Map(), None))
//> res3: demo.ScalaDsl.HttpResponse = HttpResponse(404,Map(),Some(Not Found))
/*
* Request middleware
*/
type Middleware = PartialFunction[HttpRequest, HttpRequest]
type Directive = Route => Route
val notAuthorized: Route = {
case _ => HttpResponse(401, Map(), Some("Not Authorized"))
}
//> notAuthorized : demo.ScalaDsl.Route = <function1>
val authentication: Middleware = {
case req @ HttpRequest(_, headers, _) if headers.contains("Authorization") => req
}
//> authentication : demo.ScalaDsl.Middleware = <function1>
val authenticate: Directive = route => (authentication andThen route) orElse notAuthorized
//> authenticate : demo.ScalaDsl.Directive = <function1>
val secureServer = authenticate(hello orElse notFound)
//> secureServer : demo.ScalaDsl.Route = <function1>
secureServer(HttpRequest("/hello", Map(), Some("Scala")))
//> res4: demo.ScalaDsl.HttpResponse = HttpResponse(401,Map(),Some(Not Authorized))
secureServer(HttpRequest("/hello", Map("Authorization" -> ""), Some("Scala")))
//> res5: demo.ScalaDsl.HttpResponse = HttpResponse(200,Map(),Some(Hello Scala!))
/*
* Method overloading using the Magnet pattern
* http://spray.io/blog/2012-12-13-the-magnet-pattern/
*
* Here we achieve method overloading on a generic type that otherwise would
* not be possible due to JVM's generics type erasure. The problem is too
* artificial for this simplistic example though.
*/
trait Completer {
def apply(): Route
}
import scala.util.{ Try, Success, Failure }
object Completer {
implicit def fromTryString(body: Try[String]) = new Completer {
def apply(): Route = {
case _ => body match {
case Success(b) => HttpResponse(200, Map(), Some(b))
case Failure(_) => HttpResponse(500, Map(), Some("Oops!"))
}
}
}
implicit def fromTryStatus(status: Try[Int]) = new Completer {
def apply(): Route = {
case _ => status match {
case Success(s @ 401) => HttpResponse(s, Map(), Some("Not Authorized"))
case Success(s @ 404) => HttpResponse(s, Map(), Some("Not Found"))
case Success(s) => HttpResponse(s, Map(), None)
case Failure(_) => HttpResponse(500, Map(), Some("Oops!"))
}
}
}
}
def complete(completer: Completer): Route = {
completer()
}
//> complete: (completer: demo.ScalaDsl.Completer)demo.ScalaDsl.Route
val hello2 = complete(Try("Hello Scala!"))
//> hello2 : demo.ScalaDsl.Route = <function1>
val notFound2 = complete(Try(404))
//> notFound2 : demo.ScalaDsl.Route = <function1>
val server2 = authenticate (hello2 orElse notFound2)
//> server2 : demo.ScalaDsl.Route = <function1>
/*
* Request extraction
*/
def path(p: String): Directive = {
route =>
val m: Middleware = { case req @ HttpRequest(`p`, _, _) => req }
m andThen route
}
//> path: (p: String)demo.ScalaDsl.Directive
def extractBody(route: String => Route): Route = {
case req @ HttpRequest(_, _, Some(body)) => route(body)(req)
}
//> extractBody: (route: String => demo.ScalaDsl.Route)demo.ScalaDsl.Route
val hello3 = path("/hello") { extractBody { name => complete(Try(s"Hello $name!")) } }
//> hello3 : demo.ScalaDsl.Route = <function1>
val goodbye = path("/goodbye") { extractBody { name => complete(Try(s"Goodbye $name!")) } }
//> goodbye : demo.ScalaDsl.Route = <function1>
val server3 = authenticate (hello3 orElse goodbye orElse notFound2)
//> server3 : demo.ScalaDsl.Route = <function1>
server3(HttpRequest("/hello", Map("Authorization" -> ""), Some("Scala")))
//> res6: demo.ScalaDsl.HttpResponse = HttpResponse(200,Map(),Some(Hello Scala!))
server3(HttpRequest("/foo", Map("Authorization" -> ""), Some("Scala")))
//> res7: demo.ScalaDsl.HttpResponse = HttpResponse(404,Map(),Some(Not Found))
server3(HttpRequest("/foo", Map(), Some("Scala")))
//> res8: demo.ScalaDsl.HttpResponse = HttpResponse(401,Map(),Some(Not Authorized))
server3(HttpRequest("/goodbye", Map("Authorization" -> ""), Some("Scala")))
//> res9: demo.ScalaDsl.HttpResponse = HttpResponse(200,Map(),Some(Goodbye Scala!))
}
@shishkin
Copy link
Author

The web server DSL is inspired by the Spray Routing DSL. Due to time constraints and the information overflow HLists we not touched in the demo.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment