Last active
December 31, 2017 17:42
-
-
Save friedbrice/b08d5e5660a824e3f912d4570cdf7f8c to your computer and use it in GitHub Desktop.
Exception Control Flow - Scala
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
import Spec._ | |
import Undefined._ | |
object Continuations { | |
def getUser(req: Request)(cont: User => Response): Response = { | |
val token = req.header.get("Authorization") | |
if (token.isEmpty) noToken else | |
if (`malformed token?`) malformedToken(token.get) else | |
if (`user not found?`) noUser(token.get) else | |
cont(`the user`) | |
} | |
def getResource(req: Request)(cont: Resource => Response): Response = { | |
val path: String = req.path | |
if (`resource not found?`) noResource(path) else | |
cont(`the resource`) | |
} | |
def execute(content: String, usr: User, src: Resource) | |
(cont: Unit => Response): Response = { | |
if (!`is permitted?`) notPermitted(src.path) else | |
if (!`is executed?`) badConnection else | |
cont(()) | |
} | |
def handlePost(req: Request): Response = { | |
if (req.method != "POST") notAllowed(req.method) else | |
if (req.body.isEmpty) noBody else | |
getUser(req) { usr => | |
getResource(req) { src => | |
execute(req.body, usr, src) { _ => | |
success(req.path, req.body) } } } | |
} | |
} |
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
import Spec._ | |
import Undefined._ | |
object Eithers { | |
def failIf[A](p: Boolean, a: => A): Either[A, Unit] = | |
if (p) Left(a) else Right(()) | |
def getUser(req: Request): Either[Response, User] = for { | |
token <- req.header.get("Authorization").toRight(noToken) | |
_ <- failIf(`malformed token?`, malformedToken(token)) | |
_ <- failIf(`user not found?`, noUser(token)) | |
} yield `the user` | |
def getResource(req: Request): Either[Response, Resource] = for { | |
path <- Right(req.path) | |
_ <- failIf(`resource not found?`, noResource(path)) | |
} yield `the resource` | |
def execute(content: String, usr: User, src: Resource): | |
Either[Response, Unit] = for { | |
_ <- failIf(! `is permitted?`, notPermitted(src.path)) | |
_ <- failIf(! `is executed?`, badConnection) | |
} yield {} | |
def handlePost(req: Request): Response = { | |
for { | |
_ <- failIf(req.method != "POST", notAllowed(req.method)) | |
_ <- failIf(req.body.isEmpty, noBody) | |
usr <- getUser(req) | |
src <- getResource(req) | |
_ <- execute(req.body, usr, src) | |
} yield success(req.path, req.body) | |
}.fold(identity, identity) | |
} |
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
import Spec._ | |
import Undefined._ | |
object Exceptions { | |
case class NoTokenProvided() extends Exception | |
case class MalformedToken(token: String) extends Exception | |
case class NoUserFound(token: String) extends Exception | |
@throws[NoTokenProvided] | |
@throws[MalformedToken] | |
@throws[NoUserFound] | |
def getUser(req: Request): User = { | |
val token = req.header.get("Authorization") | |
if (token.isEmpty) throw NoTokenProvided() | |
if (`malformed token?`) throw MalformedToken(token.get) | |
if (`user not found?`) throw NoUserFound(token.get) | |
`the user` | |
} | |
case class NoResourceFound(path: String) extends Exception | |
@throws[NoResourceFound] | |
def getResource(req: Request): Resource = { | |
lazy val path: String = req.path | |
if (`resource not found?`) throw NoResourceFound(path) | |
`the resource` | |
} | |
case class NotPermitted(path: String) extends Exception | |
case class BadConnection() extends Exception | |
@throws[NotPermitted] | |
@throws[BadConnection] | |
def execute(content: String, usr: User, src: Resource): Unit = { | |
if (! `is permitted?`) throw NotPermitted(src.path) | |
if (! `is executed?`) throw BadConnection() | |
} | |
case class MethodNotAllowed(method: String) extends Exception | |
case class NoBodyProvided() extends Exception | |
@throws[MethodNotAllowed] | |
@throws[NoBodyProvided] | |
def checkPreconditions(req: Request): Unit = { | |
if (req.method != "POST") throw MethodNotAllowed(req.method) | |
if (req.body.isEmpty) throw NoBodyProvided() | |
} | |
def handlePost(req: Request): Response = try { | |
checkPreconditions(req) | |
val user = getUser(req) | |
val resource = getResource(req) | |
execute(req.body, user, resource) | |
success(req.path, req.body) | |
} catch { | |
case MethodNotAllowed(method) => notAllowed(method) | |
case NoBodyProvided() => noBody | |
case NoTokenProvided() => noToken | |
case MalformedToken(token) => malformedToken(token) | |
case NoUserFound(token) => noUser(token) | |
case NoResourceFound(path) => noResource(path) | |
case NotPermitted(path) => notPermitted(path) | |
case BadConnection() => badConnection | |
} | |
} |
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
import Spec._ | |
import Undefined._ | |
object Prototype { | |
// crashes computer if no token, token malformed, or no associated user | |
def getUser(req: Request): User = { | |
`the user` | |
} | |
// crashes computer if resource not found | |
def getResource(req: Request): Resource = { | |
`the resource` | |
} | |
// crashes computer if user does not have permission or bad connection | |
def execute(content: String, u: User, r: Resource): Unit = { | |
`is executed?` | |
} | |
// crashes computer if method is not POST, body is empty, or any of the reasons above | |
def handlePost(req: Request): Response = { | |
if (req.method != "POST") `halt and catch fire` | |
if (req.body.isEmpty) `halt and catch fire` | |
val usr = getUser(req) | |
val src = getResource(req) | |
execute(req.body, usr, src) | |
success(req.path, req.body) | |
} | |
} |
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
object Spec { | |
trait User { def token: String } | |
trait Resource { def path: String } | |
case class Request( | |
path: String, | |
method: String, | |
body: String, | |
header: Map[String, String] | |
) | |
case class Response(code: Int, content: String) | |
def success(path: String, body: String): Response = | |
Response(200, s"Successfully posted $body to $path") | |
val noBody: Response = | |
Response(400, "You must provide a request body") | |
val noToken: Response = | |
Response(401, "You must provide an authorization header field") | |
def malformedToken(token: String): Response = | |
Response(401, s"Provided token is malformed: $token") | |
def noUser(token: String): Response = | |
Response(401, s"No user found for token: $token") | |
def notPermitted(path: String): Response = | |
Response(403, s"You do not have permission on $path") | |
def noResource(path: String): Response = | |
Response(404, s"No resource found for path: $path") | |
def notAllowed(method: String): Response = | |
Response(405, s"Method not allowed: $method") | |
val badConnection: Response = | |
Response(503, "Connection error, please try again later") | |
} |
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
import Spec._ | |
object Test extends App { | |
val request1 = Request("path", "POST", "", Map("Authorization" -> "hunter2")) | |
val request2 = Request("path", "POST", "body", Map("Nope" -> "nada")) | |
val request3 = Request("path", "FOO", "body", Map("Authorization" -> "hunter2")) | |
println("") | |
println("Testing Exceptions:") | |
println(" " + Exceptions.handlePost(request1)) | |
println(" " + Exceptions.handlePost(request2)) | |
println(" " + Exceptions.handlePost(request3)) | |
println("Passed.") | |
println("") | |
println("Testing Continuations:") | |
println(" " + Continuations.handlePost(request1)) | |
println(" " + Continuations.handlePost(request2)) | |
println(" " + Continuations.handlePost(request3)) | |
println("Passed.") | |
println("") | |
println("Testing Eithers:") | |
println(" " + Eithers.handlePost(request1)) | |
println(" " + Eithers.handlePost(request2)) | |
println(" " + Eithers.handlePost(request3)) | |
println("Passed.") | |
println("") | |
} |
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
import Spec._ | |
object Undefined { | |
def `malformed token?`: Boolean = ??? | |
def `user not found?`: Boolean = ??? | |
def `the user`: User = ??? | |
def `resource not found?`: Boolean = ??? | |
def `the resource`: Resource = ??? | |
def `is permitted?`: Boolean = ??? | |
def `is executed?`: Boolean = ??? | |
def `halt and catch fire`: Nothing = ??? | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment