-
Notifications
You must be signed in to change notification settings - Fork 570
Expand file tree
/
Copy pathS3Async.scala
More file actions
60 lines (51 loc) · 2.18 KB
/
Copy pathS3Async.scala
File metadata and controls
60 lines (51 loc) · 2.18 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package services
import com.gu.etagcaching.aws.s3.ObjectId
import common.GuLogging
import conf.Configuration
import play.api.libs.json.{JsError, JsSuccess, Json, Reads}
import services.S3.logS3ExceptionWithDevHint
import software.amazon.awssdk.core.async.AsyncResponseTransformer
import software.amazon.awssdk.services.s3.model.{GetObjectRequest, GetObjectResponse, NoSuchKeyException, S3Exception}
import utils.AWSv2
import scala.concurrent.{ExecutionContext, Future}
import scala.jdk.FutureConverters._
import scala.io.Codec
trait S3Async extends GuLogging {
lazy val bucket: String = Configuration.aws.frontendStoreBucket
lazy private val client = AWSv2.S3Async
def handleS3Errors[T](key: String)(future: Future[T])(implicit ec: ExecutionContext): Future[T] = {
val objectId = ObjectId(bucket, key)
future.recoverWith {
case e: NoSuchKeyException =>
log.warn(s"not found at ${objectId.s3Uri}")
Future.failed(e)
case e: S3Exception =>
logS3ExceptionWithDevHint(objectId, e)
Future.failed(e)
}
}
private def getResponse(
key: String,
)(implicit codec: Codec, ec: ExecutionContext): Future[(GetObjectResponse, String)] = {
val request = GetObjectRequest.builder().bucket(bucket).key(key).build()
val responseFutureJava = client.getObject(request, AsyncResponseTransformer.toBytes[GetObjectResponse]())
responseFutureJava.asScala.map { responseBytes =>
val objectResponse = responseBytes.response()
log.debug(s"S3 got ${objectResponse.contentLength} bytes from $key")
val content = new String(responseBytes.asByteArray(), codec.charSet)
(objectResponse, content)
}
}
def getObjectAsJson[T: Reads](key: String)(implicit ec: ExecutionContext): Future[T] = {
val futureResponse = getResponse(key)(Codec.UTF8, ec).map(_._2).flatMap { jsonString =>
val parsedJson = Json.parse(jsonString)
parsedJson.validate[T] match {
case JsSuccess(parsedObject, _) =>
Future.successful(parsedObject)
case JsError(errors) =>
Future.failed(new RuntimeException(s"Failed to parse JSON for key $key. Errors: $errors"))
}
}
handleS3Errors(key)(futureResponse)
}
}