Created
October 5, 2012 20:20
-
-
Save oscarrenalias/3842138 to your computer and use it in GitHub Desktop.
This is small implementation of a tiny MVC-oriented framework inspired by the design * of the Play Framework 2.0; it is meant as an educational exercise to get more familiar with * the concepts of partial functions, function composition and generic type
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * This is small implementation of a tiny MVC-oriented framework inspired by the design | |
| * of the Play Framework 2.0; it is meant as an educational exercise to get more familiar with | |
| * the concepts of partial functions, function composition and generic types in Scala | |
| * | |
| * How to run: | |
| * scalac framework.scala | |
| * scala App | |
| */ | |
| package object Framework { | |
| /** | |
| * Base class that represents a simplified HTTP request, which can be typed based | |
| * the contents of its body | |
| */ | |
| case class Request[+A](uri: String, body: A) | |
| /** | |
| * Base class that represents an HTTP request; ideally headers (like the content type) should be | |
| * implemented differently but this is enough for now. | |
| * The Result class is not typed because everything are strings. | |
| */ | |
| case class Result(body: String, status: Int = 200, contentType: String = "text/plain") | |
| /** | |
| * Base abstract trait that represents actions in an MVC framework. Implemented as a function that takes | |
| * a request and returns a result | |
| */ | |
| trait Action[A] extends (Request[A] => Result) { | |
| def apply(request: Request[A]): Result | |
| } | |
| /** | |
| * Type alias that will be used for testing | |
| */ | |
| type AnyContent = String | |
| /** | |
| * This object contains static methods that help generate Action classes. | |
| */ | |
| object Action { | |
| /** | |
| * Generates an Action class given a request parameter and a function passed as a closure | |
| */ | |
| def apply[A](code: Request[A] => Result):Action[A] = new Action[A] { | |
| def apply(request: Request[A]) = code(request) | |
| } | |
| /** | |
| * Generates an Action class when no request parameter is required | |
| */ | |
| def apply(code: => Result):Action[AnyContent] = Action[AnyContent](_ => code) | |
| } | |
| /** | |
| * A route links a URI to a specific action. Routes are defined as partial functions, whereby | |
| * a route is defined for a specific URI if the route's URI and the given URI are the same and if so, | |
| * then the action associated to the URI is returned. | |
| * | |
| * As they are defined as partial functions which are only defined for a specifc URI/path, they | |
| * will be composed into a single partial function that is defined for all the inputs of the | |
| * chained functions using 'orElse' | |
| */ | |
| case class Route[A](path: String, action: Action[A]) extends PartialFunction[String, Action[A]] { | |
| /** | |
| * If the given URI and the route's URI match, then the function is defined | |
| */ | |
| def isDefinedAt(uri:String) = path == uri | |
| /** | |
| * Returns the given action; please be aware that isDefinedAt must be checked before calling this | |
| * method to make sure that the route can actually process the URI | |
| */ | |
| def apply(path:String) = action | |
| } | |
| /** | |
| * This abstract trait represents a wired and configured application, which in the current implementation | |
| * is only a partial function representing chained routes and the default action that is triggered if | |
| * no route matches | |
| * | |
| * TODO: routes should be of type Route[A] but instead it's a ParticalFunction - check why | |
| */ | |
| trait FrameworkApplication[A] { | |
| val routes: PartialFunction[String, Action[A]] | |
| /** | |
| * This is a default action that will get executed if none of the configured actions matches | |
| */ | |
| val defaultAction = Action[AnyContent] { request => | |
| Result("No matching route was found for the following request: " + request, 500) | |
| } | |
| } | |
| /** | |
| * This 'runs' an application, given an Application object as well as a request | |
| * | |
| * Internally it 'lifts' the partial function with the routes so that it returns None if no | |
| * route matches (instead of a match error), so that we can execute the default action as configured | |
| * within the application instead. | |
| */ | |
| def runApp[A <: AnyContent](app:FrameworkApplication[A], request: Request[A]) = | |
| app.routes.lift(request.uri).getOrElse(app.defaultAction)(request) | |
| } | |
| package object Application { | |
| import Framework._ | |
| /** | |
| * Our toy controller, seen as a bunch of action/methods | |
| */ | |
| object Controller { | |
| def index = Action { | |
| Result("This is the index page") | |
| } | |
| def login = Action { | |
| Result("This is the login page") | |
| } | |
| def test = Action[AnyContent] { request => | |
| Result("Request body was: " + request.body) | |
| } | |
| } | |
| /** | |
| * Our application's runner, where we wire the application's routes and return a curried version of the | |
| * runApp method instead so that we can reuse it with different requests for testing purposes. This is not | |
| * strictly necessary but it makes testing easier. | |
| */ | |
| val run = runApp(new FrameworkApplication[AnyContent] { | |
| val routes = Route("/", Controller.index) orElse Route("/login", Controller.login) orElse Route("/test", Controller.test) | |
| }, _:Request[AnyContent]) | |
| } | |
| object App { | |
| import Framework._ | |
| // Our test requests | |
| val indexRequest = Request("/", "index") | |
| val loginRequest = Request("/login", "login") | |
| val failRequest = Request("/asfasdfsf", "foo") | |
| val testRequest = Request("/test", "this is the request body") | |
| def main (args: Array[String]) = { | |
| println(Application.run(indexRequest)) // works | |
| println(Application.run(loginRequest)) // works | |
| println(Application.run(testRequest)) // works, returns the request body | |
| println(Application.run(failRequest)) // returns the default rule | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment