app/model/commands/CommandException.scala (84 lines of code) (raw):

package model.commands import com.google.api.client.googleapis.json.{GoogleJsonError, GoogleJsonResponseException} import com.google.api.client.http.{HttpHeaders, HttpResponseException} import com.gu.media.logging.Logging import play.api.mvc.{Result, Results} import scala.jdk.CollectionConverters._ case class CommandException(msg: String, responseCode: Int) extends RuntimeException(msg) object CommandExceptions extends Results { def UnknownFailure = throw new CommandException("Unknown internal server error", 500) def AtomNotFound = throw new CommandException("Atom not found", 404) def AtomIdConflict = throw new CommandException("Atom ID conflict", 400) def AtomDataStoreError(err: String) = throw new CommandException(err, 500) def YouTubeConnectionIssue = throw new CommandException("Could not connect to YouTube", 500) def NotYoutubeAsset = throw new CommandException("Asset is not a youtube video", 400) def AssetVersionConflict = throw new CommandException("Asset version conflict", 400) def AssetAlreadyAdded = throw new CommandException("Asset has already been added to this atom", 400) def AssetParseFailed = throw new CommandException("Failed to parse asset", 400) def AssetNotFound = throw new CommandException("Asset not found", 404) def CannotDeleteActiveAsset = throw new CommandException("Cannot delete active asset", 400) def AssetNotFound(assetId: String) = throw new CommandException(s"Asset with id $assetId not found", 404) def YoutubeException(err: String) = throw new CommandException(s"Exception when trying to reach YouTube: $err", 400) def AtomUpdateFailed(err: String) = throw new CommandException(s"Failed to update atom: $err", 500) def AtomUpdateConflictError(err: String) = throw new CommandException(s"Failed to update atom: $err", 409) def AtomPublishFailed(err: String) = throw new CommandException(s"Failed to publish atom: $err", 500) def AtomMissingYouTubeChannel = throw new CommandException("Atom is missing YouTube channel", 400) def YouTubeVideoDoesNotExist(id: String) = throw new CommandException(s"YouTube video $id does not exist", 400) def IncorrectYouTubeChannel = throw new CommandException(s"New video is not on the same YouTube channel", 400) def NotGLabsAtom = throw new CommandException(s"Third party videos can only be added to GLabs Atoms", 400) def PacFileUploadFailed = throw new CommandException("Failed to upload pac file", 500) // Add exceptions here as required def commandExceptionAsResult: PartialFunction[Throwable, Result] = { case CommandException(msg, 400) => BadRequest(msg) case CommandException(msg, 404) => NotFound(msg) case CommandException(msg, 409) => Conflict(msg) case CommandException(msg, 500) => InternalServerError(msg) case YouTubeError(msg, true) => InternalServerError(msg) case YouTubeError(msg, false) => InternalServerError(msg).withHeaders("X-No-Alerts" -> "true") } } object YouTubeError extends Logging { def unapply(err: Throwable): Option[(String, Boolean)] = err match { case e: GoogleJsonResponseException => (e.getDetails.getCode, getDomain(e)) match { case (503, Some("global")) => Some((noAlerts(e), false)) case (403, Some("usageLimits")) => Some((noAlerts(e), false)) case (400, Some("youtubePartner.videoAdvertisingOptions.get")) => { Some((noAlerts(e), false)) } case (code, _) => val message = Option(e.getDetails.getMessage).getOrElse("unknown") log.warn(s"YouTube failure. Code: $code. Message: $message") Some((s"YouTube $code: $message", true)) // alerts } case _ => None } private def getDomain(e: GoogleJsonResponseException): Option[String] = { val errors = e.getDetails.getErrors.asScala errors.headOption.flatMap { err => Some(err.getDomain) } } // For testing val rateLimitExceeded: GoogleJsonResponseException = buildYouTubeException(403, "usageLimits", "User Rate Limit Exceeded", "userRateLimitExceeded") val backendError: GoogleJsonResponseException = buildYouTubeException(503, "global", "Backend Error", "backendError") private def buildYouTubeException(code: Int, domain: String, message: String, reason: String): GoogleJsonResponseException = { val info = new GoogleJsonError.ErrorInfo() info.setDomain(domain) info.setMessage(message) info.setReason(reason) val error = new GoogleJsonError() error.setCode(code) error.setErrors(List(info).asJava) error.setMessage(message) val builder = new HttpResponseException.Builder(503, null, new HttpHeaders()) builder.setMessage(message) new GoogleJsonResponseException(builder, error) } private def noAlerts(e: GoogleJsonResponseException): String = { val msg = s"YouTube ${e.getDetails.getCode} ${e.getDetails.getMessage}" log.warn(msg, e) // to get stack trace in the logs msg } }