def exampleFunction(test: String, callback: () => Boolean, message: String): Any = {
if ("yes" == test) {
if (callback()) message
} else if ("no" == test) "no"
else false
}
APIs are the new classes
def updateUser(userId: String) = Action { request =>
val access: Access = Authentication.getAccess(request, userId)
if (access.isOk) {
val submission: User = UserData.fromSubmission(request)
val updatedUser: User = UserRepository.update(userId, submission)
val userResponse = UserResponse(updatedUser)
Ok(Json.toJson(userResponse))
} else {
Forbidden("Access denied")
}
}
def updateUser(userId: String) = Action { request =>
val access: Access = Authentication.getAccess(request, userId)
if (access.isOk) {
try {
val submission: User = UserData.fromSubmission(request)
val updatedUser: User = UserRepository.update(userId, submission)
val userResponse = UserResponse(updatedUser)
Ok(Json.toJson(userResponse))
} catch {
case e: UserData.Invalid => BadRequest("Invalid user data")
case e: UserData.InvalidEmailAddress => BadRequest("Invalid email address")
case e: DatabaseError => InternalServerError("Could not connect to database")
}
} else {
Forbidden("Access denied")
}
}
def updateUser(userId: String) = Action { request =>
val access = Authentication.getAccess(request, userId)
if (access.isOk) {
try {
val (errors, submissionOption) = UserData.attemptFromSubmission(request)
if (submissionOption.isDefined) {
val updatedUser = UserRepository.update(userId, submissionOption.get)
val userResponse = UserResponse(updatedUser)
Ok(Json.toJson(userResponse))
} else {
val submissionErrors = ValidationErrorsResponse(errors)
BadRequest(Json.toJson(submissionErrors))
}
} catch {
case e: UserData.Invalid => BadRequest("Invalid user data")
case e: DatabaseError => InternalServerError("Could not connect to database")
}
} else {
Forbidden("Access denied")
}
}
def updateUser(userId: String) = Action { request =>
val access = Authentication.getAccess(request, userId)
if (access.isOk) {
try {
val (errors, submissionOption) = UserData.attemptFromSubmission(request)
if (submissionOption.isDefined) {
val updatedUser = UserRepository.update(userId, submissionOption.get)
val userResponse = UserResponse(updatedUser)
Ok(Json.toJson(userResponse))
} else {
val submissionErrors = ValidationErrorsResponse(errors)
BadRequest(Json.toJson(submissionErrors))
}
} catch {
case e: UserData.Invalid => BadRequest("Invalid user data")
case e: DatabaseError => InternalServerError("Could not connect to database")
}
} else {
Forbidden("Access denied")
}
}
def updateUser(userId: String) = Action { request =>
val access = Authentication.getAccess(request, userId)
if (access.isOk) {
try {
val (errors, submissionOption) = UserData.attemptFromSubmission(request)
if (submissionOption.isDefined) {
val updatedUser = UserRepository.update(userId, submissionOption.get)
val userResponse = UserResponse(updatedUser)
Ok(Json.toJson(userResponse))
} else {
val submissionErrors = ValidationErrorsResponse(errors)
BadRequest(Json.toJson(submissionErrors))
}
} catch {
case e: UserData.Invalid => BadRequest("Invalid user data")
case e: DatabaseError => InternalServerError("Could not connect to database")
}
} else {
Forbidden("Access denied")
}
}
def updateUser(userId: String) = Action { request =>
val access = Authentication.getAccess(request, userId)
if (access.isOk) {
try {
val (errors, submissionOption) = UserData.attemptFromSubmission(request)
if (submissionOption.isDefined) {
val updatedUser = UserRepository.update(userId, submissionOption.get)
val userResponse = UserResponse(updatedUser)
Ok(Json.toJson(userResponse))
} else {
val submissionErrors = ValidationErrorsResponse(errors)
BadRequest(Json.toJson(submissionErrors))
}
} catch {
case e: UserData.Invalid => BadRequest("Invalid user data")
case e: DatabaseError => InternalServerError("Could not connect to database")
}
} else {
Forbidden("Access denied")
}
}
def updateUser(userId: String) = Action { request =>
val access = Authentication.getAccess(request, userId)
if (access.isOk) {
try {
val (errors, submissionOption) = UserData.attemptFromSubmission(request)
if (submissionOption.isDefined) {
val updatedUser = UserRepository.update(userId, submissionOption.get)
val userResponse = UserResponse(updatedUser)
Ok(Json.toJson(userResponse))
} else {
val submissionErrors = ValidationErrorsResponse(errors)
BadRequest(Json.toJson(submissionErrors))
}
} catch {
case e: UserData.Invalid => BadRequest("Invalid user data")
case e: DatabaseError => InternalServerError("Could not connect to database")
}
} else {
Forbidden("Access denied")
}
}
Either a Left type L
or a Right type R
Either[L, R]
Option[R]
Left(failureValue)
Right(happyValue)
Either an error or a successful response.
case class ApiErrors(errors: List[ApiError]) {
def statusCode = errors.max(_.statusCode)
}
case class ApiError(message: String, friendlyMessage: String,
statusCode: Int, context: Option[String] = None)
type ApiResponse[T] = Either[ApiErrors, T]
{
"status": "ok",
"response": {
"id": "100001",
"username": "user",
"email": "user@example.com",
"created": "2011-11-16T15:01:30Z",
...
}
}
{
"status": "error",
"statusCode": 500,
"errors": [
{
"message": "Failed to connect to database",
"friendlyMessage": "An error occurred saving your data, please try again shortly"
}
]
}
object ApiResponse extends Results {
def apply[T](action: => ApiResponse[T])(implicit tjs: Writes[T]): Result = {
action.fold(
apiErrors =>
Status(apiErrors.statusCode) {
JsObject(Seq(
"status" -> JsString("error"),
"statusCode" -> JsNumber(apiErrors.statusCode),
"errors" -> Json.toJson(apiErrors.errors)
))
},
t =>
Ok {
JsObject(Seq(
"status" -> JsString("ok"),
"response" -> Json.toJson(t)
))
}
)
}
}
object Authentication {
def getAccess(request: RequestHeader, userId: String): Access = {
// check for auth cookie
// check for access token
// check credentials in database
throw new DatabaseError()
// check credentials match userId
Access.OK
}
}
object Authentication {
def getAccess(request: RequestHeader, userId: String): ApiResponse[Access] = {
// check for auth cookie
// check for access token
// ensure valid credentials
Left(ApiErrors(List(ApiError("Database connection failure", "We were unable to check your credentials, please try again shortly", 500))))
// check credentials match userId
Right(Access.OK)
}
}
def updateUser(userId: String) = Action { request =>
val access = Authentication.getAccess(request, userId)
if (access.isOk) {
try {
val (errors, submissionOption) = UserData.attemptFromSubmission(request)
if (submissionOption.isDefined) {
val updatedUser = UserRepository.update(userId, submissionOption.get)
val userResponse = UserResponse(updatedUser)
Ok(Json.toJson(userResponse))
} else {
val submissionErrors = ValidationErrorsResponse(errors)
BadRequest(Json.toJson(submissionErrors))
}
} catch {
case e: UserData.Invalid => BadRequest("Invalid user data")
case e: DatabaseError => InternalServerError("Could not connect to database")
}
} else {
Forbidden("Access denied")
}
}
def updateUser(userId: String) = Action { request =>
ApiResponse {
for {
access <- Authentication.getAccess(request, userId).right
submission <- UserData.fromSubmission(request).right
updatedUser <- UserRepository.update(userId, submission).right
} yield updatedUser
}
}
At some point in the Future,
Either an error or a successful response.
type ApiResponse[T] = Future[Either[ApiErrors, T]]
case class ApiResponse[A] private (underlying: Future[Either[ApiErrors, A]]) {
def map[B](f: A => B)(implicit ec: ExecutionContext): ApiResponse[B] =
???
def flatMap[B](f: A => ApiResponse[B])(implicit ec: ExecutionContext): ApiResponse[B] =
???
def fold[B](failure: ApiErrors => B, success: A => B)(implicit ec: ...): Future[B] =
???
...
}
case class ApiResponse[A] private (underlying: Future[Either[ApiErrors, A]]) {
def map[B](f: A => B)(implicit ec: ExecutionContext): ApiResponse[B] =
flatMap(a => ApiResponse.ApiRight(f(a)))
def flatMap[B](f: A => ApiResponse[B])
(implicit ec: ExecutionContext): ApiResponse[B] = ApiResponse {
asFuture.flatMap {
case Right(a) => f(a).asFuture
case Left(e) => Future.successful(Left(e))
}
}
def fold[B](failure: ApiErrors => B, success: A => B)
(implicit ec: ExecutionContext): Future[B] = {
asFuture.map(_.fold(failure, success))
}
def asFuture(implicit ec: ExecutionContext): Future[Either[ApiErrors, A]] = {
underlying recover { case err =>
val apiErrors = ApiErrors(List(ApiError.unexpected(err.getMessage)))
scala.Left(apiErrors)
}
}
}
object ApiResponse extends Results {
def apply[T](action: => ApiResponse[T])
(implicit tjs: Writes[T], ec: ExecutionContext): Future[Result] = {
action.fold(
err =>
Status(err.statusCode) {
JsObject(Seq(
"status" -> JsString("error"),
"statusCode" -> JsNumber(err.statusCode),
"errors" -> Json.toJson(err.errors)
))
},
t =>
Ok {
JsObject(Seq(
"status" -> JsString("ok"),
"response" -> Json.toJson(t)
))
}
)
}
}
object ApiResponse {
def Right[A](a: A): ApiResponse[A] =
ApiResponse(Future.successful(scala.Right(a)))
def Left[A](err: ApiErrors): ApiResponse[A] =
ApiResponse(Future.successful(scala.Left(err)))
object Async {
def Right[A](fa: Future[A])(implicit ec: ExecutionContext): ApiResponse[A] =
ApiResponse(fa.map(scala.Right(_)))
def Left[A](ferr: Future[ApiErrors])(implicit ec: ExecutionContext): ApiResponse[A] =
ApiResponse(ferr.map(scala.Left(_)))
}
}
object Authentication {
def getAccess(request: RequestHeader, userId: String): ApiResponse[Access] = {
// check for auth cookie
// check for access token
// ensure valid credentials
ApiResponse.Left(ApiErrors(List(ApiError("Database connection failure", "We were unable to check your credentials, please try again shortly", 500))))
// check credentials match userId
ApiResponse.Right(Access.OK)
}
}
def updateUser(userId: String) = Action.async { request =>
ApiResponse {
for {
access <- Authentication.getAccess(request, userId)
submission <- UserData.fromSubmission(request)
updatedUser <- UserRepository.update(userId, submission)
} yield updatedUser
}
}
def updateUser(userId: String) = Action { request =>
val access = Authentication.getAccess(request, userId)
if (access.isOk) {
try {
val (errors, submissionOption) = UserData.attemptFromSubmission(request)
if (submissionOption.isDefined) {
val updatedUser = UserRepository.update(userId, submissionOption.get)
val userResponse = UserResponse(updatedUser)
Ok(Json.toJson(userResponse))
} else {
val submissionErrors = ValidationErrorsResponse(errors)
BadRequest(Json.toJson(submissionErrors))
}
} catch {
case e: UserData.Invalid => BadRequest("Invalid user data")
case e: DatabaseError => InternalServerError("Could not connect to database")
}
} else {
Forbidden("Access denied")
}
}
def updateUser(userId: String) = Action.async { request =>
ApiResponse {
for {
access <- Authentication.getAccess(request, userId)
submission <- UserData.fromSubmission(request)
updatedUser <- UserRepository.update(userId, submission)
} yield updatedUser
}
}