Repository: softwaremill/elasticmq
Pull request #535 (2021-09-22 07:05:35)

Introduced limit for message attributes
Connected with [issue 320](https://github.com/softwaremill/elasticmq/issues/320)

Now sending message with more than 10 message attributes is unsuccessful.


Patch:
diff --git a/core/src/main/scala/org/elasticmq/Limits.scala b/core/src/main/scala/org/elasticmq/Limits.scala
index 0af01876..e23b555d 100644
--- a/core/src/main/scala/org/elasticmq/Limits.scala
+++ b/core/src/main/scala/org/elasticmq/Limits.scala
@@ -12,6 +12,7 @@ trait Limits {
   def numberAttributeValueLimit: Limit[RangeLimit[BigDecimal]]
   def messageWaitTimeLimit: Limit[RangeLimit[Long]]
   def maxMessageLength: Limit[Int]
+  def messageAttributesLimit: Limit[Int]
 }
 
 case object StrictSQSLimits extends Limits {
@@ -19,6 +20,7 @@ case object StrictSQSLimits extends Limits {
   override val batchSizeLimit: Limit[Int] = LimitedValue(10)
   override val numberOfMessagesLimit: Limit[RangeLimit[Int]] = LimitedValue(RangeLimit(1, 10))
   override val nonEmptyAttributesLimit: Limit[Boolean] = LimitedValue(true)
+  override val messageAttributesLimit: Limit[Int] = LimitedValue(10)
   override val bodyValidCharactersLimit: Limit[List[RangeLimit[Int]]] = LimitedValue(
     List(
       RangeLimit(0x9, 0x9),
@@ -44,6 +46,7 @@ case object RelaxedSQSLimits extends Limits {
   override val numberAttributeValueLimit: Limit[RangeLimit[BigDecimal]] = NoLimit
   override val messageWaitTimeLimit: Limit[RangeLimit[Long]] = NoLimit
   override val maxMessageLength: Limit[Int] = NoLimit
+  override val messageAttributesLimit: Limit[Int] = NoLimit
 }
 
 sealed trait Limit[+A]
@@ -62,6 +65,13 @@ object Limits {
       "AWS.SimpleQueueService.TooManyEntriesInBatchRequest"
     )
 
+  def verifyMessageAttributesNumber(n: Int, limits: Limits): Either[String, Unit] = {
+    validateWhenLimitAvailable(limits.messageAttributesLimit)(
+      n <= _,
+      s"Number of message attributes [$n] exceeds the allowed maximum [10]."
+    )
+  }
+
   def verifyNumberOfMessagesFromParameters(
       numberOfMessagesFromParameters: Int,
       limits: Limits
diff --git a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/SendMessageDirectives.scala b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/SendMessageDirectives.scala
index 7954c086..fd2b521a 100644
--- a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/SendMessageDirectives.scala
+++ b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/SendMessageDirectives.scala
@@ -71,6 +71,9 @@ trait SendMessageDirectives { this: ElasticMQDirectives with SQSLimitsModule =>
       .union(List(0))
       .max // even if nothing, return 0
 
+    Limits.verifyMessageAttributesNumber(numAttributes, sqsLimits)
+      .fold(error => throw new SQSException(error), identity)
+
     (1 to numAttributes).map { i =>
       val name = parameters("MessageAttribute." + i + ".Name")
       val dataType = parameters("MessageAttribute." + i + ".Value.DataType")


Test patch:
diff --git a/core/src/test/scala/org/elasticmq/LimitsTest.scala b/core/src/test/scala/org/elasticmq/LimitsTest.scala
index dd5b3e56..272db884 100644
--- a/core/src/test/scala/org/elasticmq/LimitsTest.scala
+++ b/core/src/test/scala/org/elasticmq/LimitsTest.scala
@@ -56,6 +56,22 @@ class LimitsTest extends AnyWordSpec with Matchers with EitherValues {
     }
   }
 
+  "Validate number of message attributes" should {
+    val attributesLimit = 10
+
+    "fail on exceeded sqs limit in strict mode" in {
+      Limits.verifyMessageAttributesNumber(attributesLimit, StrictSQSLimits) shouldBe Right(())
+      Limits.verifyMessageAttributesNumber(attributesLimit + 1, StrictSQSLimits) shouldBe Left(
+        "Number of message attributes [11] exceeds the allowed maximum [10]."
+      )
+    }
+
+    "pass on exceeded sqs limit in relaxed mode" in {
+      Limits.verifyMessageAttributesNumber(attributesLimit, RelaxedSQSLimits) shouldBe Right(())
+      Limits.verifyMessageAttributesNumber(attributesLimit + 1, RelaxedSQSLimits) shouldBe Right(())
+    }
+  }
+
   "Validation of message string attribute in strict mode" should {
     "pass if string attribute contains only allowed characters" in {
       val testString = List(0x9, 0xa, 0xd, 0x21, 0xe005, 0x10efff).map(_.toChar).mkString
diff --git a/rest/rest-sqs-testing-amazon-java-sdk/src/test/scala/org/elasticmq/rest/sqs/AmazonJavaSdkTestSuite.scala b/rest/rest-sqs-testing-amazon-java-sdk/src/test/scala/org/elasticmq/rest/sqs/AmazonJavaSdkTestSuite.scala
index fb0ad4c0..794ce048 100644
--- a/rest/rest-sqs-testing-amazon-java-sdk/src/test/scala/org/elasticmq/rest/sqs/AmazonJavaSdkTestSuite.scala
+++ b/rest/rest-sqs-testing-amazon-java-sdk/src/test/scala/org/elasticmq/rest/sqs/AmazonJavaSdkTestSuite.scala
@@ -889,6 +889,24 @@ class AmazonJavaSdkTestSuite extends SqsClientServerCommunication with Matchers
     res.getSequenceNumber should equal(seqNum2)
   }
 
+  test("sendMessage should throw when message has more than 10 message attributes") {
+    val sqsMessageAttributesLimit = 10
+
+    val queueUrl = client.createQueue(new CreateQueueRequest("q")).getQueueUrl
+    val attr = new MessageAttributeValue().withStringValue("str").withDataType("String")
+    val atLeastElevenMessageAttributes = (1 to 11).map(_.toString -> attr)
+      .toMap
+      .asJava
+
+    atLeastElevenMessageAttributes.size() > sqsMessageAttributesLimit shouldBe true
+
+    assertThrows[AmazonSQSException] {
+      client.sendMessage(
+        new SendMessageRequest(queueUrl, "MessageWithMoreThan10MessageAttributes")
+          .withMessageAttributes(atLeastElevenMessageAttributes))
+    }
+  }
+
   def queueVisibilityTimeout(queueUrl: String): Long = getQueueLongAttribute(queueUrl, visibilityTimeoutAttribute)
 
   test("should receive no more than the given amount of messages") {