
org.elasticmq.rest.sqs.BatchRequestsModule.scala Maven / Gradle / Ivy
package org.elasticmq.rest.sqs
import org.elasticmq.rest.sqs.Constants.QueueUrlParameter
import org.elasticmq.Limits
import spray.json.{JsonFormat, RootJsonFormat}
import spray.json.DefaultJsonProtocol._
import java.util.regex.Pattern
import scala.concurrent.Future
import scala.xml.Elem
trait BatchRequestsModule {
this: SQSLimitsModule with ActorSystemModule =>
def batchRequest[M <: BatchEntry, R](messagesData: List[M])(
single: (M, String, Int) => Future[R]
): Future[BatchResponse[R]] = {
val uniqueIds = messagesData.map(_.Id).toSet
if (uniqueIds.size != messagesData.size) {
throw SQSException.batchEntryIdsNotDistinct
}
Limits
.verifyBatchSize(uniqueIds.size, sqsLimits)
.fold(_ => throw SQSException.tooManyEntriesInBatchRequest, identity)
val result = messagesData.zipWithIndex.map {
case (messageData, index) => {
val id = messageData.Id
Future.unit
.flatMap(_ => single(messageData, id, index))
.map(Right(_))
.recoverWith { case e: SQSException =>
Future(Left(Failed(e.code, id, e.message, SenderFault = true)))
}
}
}
Future
.sequence(result)
.map(
_.foldLeft((Option.empty[List[Failed]], List.empty[R])) {
case ((failures, successes), Left(failed)) =>
(failures.map(_ :+ failed).orElse(Some(List(failed))), successes)
case ((failures, successes), Right(success)) => (failures, successes :+ success)
}
)
.map((BatchResponse.apply[R] _).tupled)
}
}
object BatchRequestsModule {
/** In the given list of parameters, lookups all parameters of the form:
* {prefix}.{discriminator}.key=value
, and for each discriminator builds a map of found key-value
* mappings.
*/
def subParametersMaps[M](parameters: Map[String, String])(implicit
flatParamsReader: BatchFlatParamsReader[M]
): List[M] = {
val subParameters = collection.mutable.Map[String, Map[String, String]]()
val keyRegexp = (Pattern.quote(flatParamsReader.batchPrefix) + "\\.([^.]+)\\.(.+)").r
parameters.foreach { case (key, value) =>
keyRegexp.findFirstMatchIn(key).map { keyMatch =>
val discriminator = keyMatch.group(1)
val subKey = keyMatch.group(2)
val subMap =
subParameters.getOrElse(discriminator, Map[String, String]())
subParameters.put(discriminator, subMap + (subKey -> value))
}
}
subParameters.toList.sortBy(_._1.toInt).map(_._2).map(flatParamsReader.read)
}
}
trait BatchEntry {
def Id: String
}
case class BatchRequest[M](
Entries: List[M],
QueueUrl: String
)
case class BatchResponse[R](Failed: Option[List[Failed]], Successful: List[R])
object BatchResponse {
implicit def jsonFormat[R: JsonFormat]: RootJsonFormat[BatchResponse[R]] = jsonFormat2(BatchResponse.apply[R])
}
object BatchRequest {
implicit def jsonFormat[M: JsonFormat]: RootJsonFormat[BatchRequest[M]] = jsonFormat2(BatchRequest.apply[M])
implicit def queryParamReader[M: BatchFlatParamsReader]: FlatParamsReader[BatchRequest[M]] =
new FlatParamsReader[BatchRequest[M]] {
override def read(params: Map[String, String]): BatchRequest[M] = {
new BatchRequest[M](
BatchRequestsModule.subParametersMaps(params),
requiredParameter(params)(QueueUrlParameter)
)
}
}
}
case class Failed(Code: String, Id: String, Message: String, SenderFault: Boolean)
object Failed {
implicit val format: RootJsonFormat[Failed] = jsonFormat4(Failed.apply)
implicit val xmlSerializer: XmlSerializer[Failed] = new XmlSerializer[Failed] {
override def toXml(t: Failed): Elem =
{t.Id}
true
{t.Code}
{t.Message}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy