hq/app/utils/attempt/Failure.scala (106 lines of code) (raw):

package utils.attempt import aws.AwsClient case class FailedAttempt(failures: List[Failure]) { def statusCode: Int = failures.map(_.statusCode).max def logMessage: String = failures.map { failure => val context = failure.context.fold("")(c => s" ($c)") val causedBy = firstException.fold("")(err => s" caused by: ${err.getMessage}") s"${failure.message}$context$causedBy" }.mkString(", ") def firstException: Option[Throwable] = { for { exceptingFailure <- failures.find(_.throwable.isDefined) throwable <- exceptingFailure.throwable } yield throwable } } object FailedAttempt { def apply(error: Failure): FailedAttempt = { FailedAttempt(List(error)) } def apply(errors: Seq[Failure]): FailedAttempt = { FailedAttempt(errors.toList) } } case class Failure( message: String, friendlyMessage: String, statusCode: Int, context: Option[String] = None, throwable: Option[Throwable] = None ) { def attempt = FailedAttempt(this) } object Failure { // Pre-defined "common" failures def awsError(serviceNameOpt: Option[String], clientContext: AwsClient[_], err: Throwable): Failure = { val context = contextString(clientContext) val details = serviceNameOpt.fold(s"AWS unknown error, unknown service (check logs for stacktrace), $context") { serviceName => s"AWS unknown error, service: $serviceName (check logs for stacktrace), $context" } val friendlyMessage = serviceNameOpt.fold("Unknown error while making API calls to AWS.") { serviceName => s"Unknown error while making an API call to AWS' $serviceName service" } Failure(details, friendlyMessage, 500, throwable = Some(err)) } def notYetLoaded(accountId: String, cacheContent: String): Failure = { val details = s"Cache service error; $cacheContent not yet loaded for $accountId" val friendlyMessage = s"We have not yet loaded the $cacheContent data for $accountId" Failure(details, friendlyMessage, 503) } def cacheServiceErrorPerAccount(accountId: String, cacheContent: String): Failure = { val details = s"Cache service error; unable to retrieve $cacheContent for $accountId" val friendlyMessage = s"Missing $cacheContent data for $accountId" Failure(details, friendlyMessage, 500) } def cacheServiceErrorAllAccounts(cacheContent: String): Failure = { val details = s"Cache service error; unable to retrieve $cacheContent" val friendlyMessage = s"Missing $cacheContent data" Failure(details, friendlyMessage, 500) } def contextString(clientContext: AwsClient[_]): String = { val acc = s"account: ${clientContext.account.name}" val reg = s"region: ${clientContext.region.id}" s"$acc, $reg" } def expiredCredentials(serviceNameOpt: Option[String], clientContext: AwsClient[_]): Failure = { val context = contextString(clientContext) val details = serviceNameOpt.fold(s"expired AWS credentials, unknown service, $context") { serviceName => s"expired AWS credentials, service: $serviceName, $context" } Failure(details, "Failed to request data from AWS, the temporary credentials have expired.", 401) } def noCredentials(serviceNameOpt: Option[String], clientContext: AwsClient[_]): Failure = { val context = contextString(clientContext) val details = serviceNameOpt.fold(s"no AWS credentials available, unknown service, $context") { serviceName => s"no credentials found, service: $serviceName, $context" } Failure(details, "Failed to request data from AWS, no credentials found.", 401) } def insufficientPermissions(serviceNameOpt: Option[String], clientContext: AwsClient[_]): Failure = { val context = contextString(clientContext) val details = serviceNameOpt.fold(s"application is not authorized to perform actions for a service, $context") { serviceName => s"application is not authorized to perform actions for service: $serviceName, $context" } val friendlyMessage = serviceNameOpt.fold("Application is not authorized to perform actions for a service") { serviceName => s"Application is not authorized to perform actions for service: $serviceName by the current access policies" } Failure(details, friendlyMessage, 403) } def rateLimitExceeded(serviceNameOpt: Option[String], clientContext: AwsClient[_]): Failure = { val context = contextString(clientContext) val details = serviceNameOpt.fold(s"rate limit exceeded while calling an AWS service, $context") { serviceName => s"rate limit exceeded while calling service: $serviceName, $context" } val friendlyMessage = serviceNameOpt.fold("Rate limit exceeded") { serviceName => s"Rate limit exceeded for service: $serviceName" } Failure(details, friendlyMessage, 429) } def awsAccountNotFound(accountId: String): Failure = { Failure( s"Unknown account $accountId", s"Couldn't find AWS with ID $accountId", 404 ) } }