Skip to content

Instantly share code, notes, and snippets.

@julien-lafont
Created March 16, 2017 07:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save julien-lafont/56da642e4cf6e2a64d9e3c0525d12db1 to your computer and use it in GitHub Desktop.
Save julien-lafont/56da642e4cf6e2a64d9e3c0525d12db1 to your computer and use it in GitHub Desktop.
Scala: pseudo "union type" with sealed trait & pattern matching
import play.api.libs.json.{JsNull, JsValue, Json}
trait FooServiceError
case object ApiNotFound extends FooServiceError
case class BackendNotAvailable(endpoint: String) extends FooServiceError
case class ErrorDuringProcessingEvent(payload: JsValue, error: String) extends FooServiceError
case class PermissionRefused(providedScopes: Seq[String], missingScopes: Seq[String]) extends FooServiceError
def callBackendToGeneratePiValue(decimal: Int): Either[FooServiceError, Double] = {
// fake code
if (scala.math.random > 0.1)
Left(ApiNotFound)
else if (scala.math.random > 0.1)
Left(BackendNotAvailable("localhost:8080/api/gateway"))
else if (scala.math.random > 0.1)
Left(ErrorDuringProcessingEvent(Json.obj(/*fake payload */), "fake business error"))
else if (scala.math.random > 0.1)
Left(PermissionRefused(Seq("read", "write"), Seq("admin")))
else
Right((scala.math.Pi * decimal).toInt / decimal)
}
def processRequest(request: Request) = {
callBackendToGeneratePiValue(request.countDecimal) match {
case Left(ApiNotFound) =>
Result(404)
case Left(BackendNotAvailable(endpoint)) =>
Result(500, Json.obj("endpoint" -> endpoint))
case Left(ErrorDuringProcessingEvent(payload, error)) =>
Result(409, Json.obj("payload" -> payload, "error" -> error))
case Left(PermissionRefused(_, missing)) =>
Result(403, Json.obj("missing" -> missing))
case Right(v) => Result(200, Json.obj("pi" -> v))
}
}
// Fake http request-response
case class Request(countDecimal: Int)
case class Result(code: Int, body: JsValue = JsNull)
import play.api.libs.json.{JsValue, Json}
trait FooServiceError {
def asJson: JsValue
}
case object ApiNotFound extends FooServiceError {
override def asJson = Json.obj("code" -> "api-not-found")
}
case class BackendNotAvailable(endpoint: String) extends FooServiceError {
override def asJson = Json.obj("code" -> "backend-not-found", "endpoint" -> endpoint)
}
case class ErrorDuringProcessingEvent(payload: JsValue, error: String) extends FooServiceError {
override def asJson = Json.obj("code" -> "business-error", "payload" -> payload, "message" -> error)
}
case class PermissionRefused(providedScopes: Seq[String], missingScopes: Seq[String]) extends FooServiceError {
override def asJson = Json.obj("code" -> "permission-refused", "scopes" -> providedScopes, "missing" -> missingScopes)
}
def callBackendToGeneratePiValue(decimal: Int): Either[FooServiceError, Double] = {
// fake code
if (scala.math.random > 0.1)
Left(ApiNotFound)
else if (scala.math.random > 0.1)
Left(BackendNotAvailable("localhost:8080/api/gateway"))
else if (scala.math.random > 0.1)
Left(ErrorDuringProcessingEvent(Json.obj(/*fake payload */), "fake business error"))
else if (scala.math.random > 0.1)
Left(PermissionRefused(Seq("read", "write"), Seq("admin")))
else
Right((scala.math.Pi * decimal).toInt / decimal)
}
def processRequest(request: Request) = {
callBackendToGeneratePiValue(request.countDecimal) match {
case Left(ApiNotFound) => Result(404, ApiNotFound.asJson)
case Left(err: BackendNotAvailable) => Result(500, err.asJson)
case Left(err: ErrorDuringProcessingEvent) => Result(409, err.asJson)
case Left(err: PermissionRefused) => Result(403, err.asJson)
case Right(pi) => Result(200, Json.obj("pi" -> pi))
}
}
// Fake http request-response
case class Request(countDecimal: Int)
case class Result(code: Int, body: JsValue)
import play.api.libs.json.{JsValue, Json}
trait FooServiceError {
def asResult: Result
val retryable: Boolean = false
}
case object ApiNotFound extends FooServiceError {
override def asResult = Result(404, Json.obj("code" -> "api-not-found"))
}
case class BackendNotAvailable(endpoint: String) extends FooServiceError {
override def asResult = Result(500, Json.obj("code" -> "backend-not-found", "endpoint" -> endpoint))
override val retryable = true
}
case class ErrorDuringProcessingEvent(payload: JsValue, error: String) extends FooServiceError {
override def asResult = Result(409, Json.obj("code" -> "business-error", "payload" -> payload, "message" -> error))
}
case class PermissionRefused(providedScopes: Seq[String], missingScopes: Seq[String]) extends FooServiceError {
override def asResult = Result(403, Json.obj("code" -> "permission-refused", "scopes" -> providedScopes, "missing" -> missingScopes))
}
def callBackendToGeneratePiValue(decimal: Int): Either[FooServiceError, Double] = {
// fake code
if (scala.math.random > 0.1)
Left(ApiNotFound)
else if (scala.math.random > 0.1)
Left(BackendNotAvailable("localhost:8080/api/gateway"))
else if (scala.math.random > 0.1)
Left(ErrorDuringProcessingEvent(Json.obj(/*fake payload */), "fake business error"))
else if (scala.math.random > 0.1)
Left(PermissionRefused(Seq("read", "write"), Seq("admin")))
else
Right((scala.math.Pi * decimal).toInt / decimal)
}
def callbackendWithRetry(decimal: Int): Either[FooServiceError, Double] = {
val callBackend = () => callBackendToGeneratePiValue(decimal)
callBackend().left.flatMap {
case err if err.retryable => callBackend()
case fatal => Left(fatal)
}
}
def processRequest(request: Request) = {
callbackendWithRetry(request.countDecimal) match {
case Left(err) => err.asResult
case Right(v) => Result(200, Json.obj("pi" -> v))
}
}
// Fake http request-response
case class Request(countDecimal: Int)
case class Result(code: Int, body: JsValue)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment