All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.elasticmq.Limits.scala Maven / Gradle / Ivy

The newest version!
package org.elasticmq

import scala.collection.JavaConverters._
import scala.util.Try

trait Limits {
  def queueNameLengthLimit: Limit[Int]
  def batchSizeLimit: Limit[Int]
  def numberOfMessagesLimit: Limit[RangeLimit[Int]]
  def nonEmptyAttributesLimit: Limit[Boolean]
  def bodyValidCharactersLimit: Limit[List[RangeLimit[Int]]]
  def numberAttributeValueLimit: Limit[RangeLimit[BigDecimal]]
  def messageWaitTimeLimit: Limit[RangeLimit[Long]]
  def maxMessageLength: Limit[Int]
  def messageAttributesLimit: Limit[Int]
}

case object StrictSQSLimits extends Limits {
  override val queueNameLengthLimit: Limit[Int] = LimitedValue(80)
  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),
      RangeLimit(0xa, 0xa),
      RangeLimit(0xd, 0xd),
      RangeLimit(0x20, 0xd7ff),
      RangeLimit(0xe000, 0xfffd),
      RangeLimit(0x10000, 0x10ffff)
    )
  )
  override val numberAttributeValueLimit: Limit[RangeLimit[BigDecimal]] = LimitedValue(
    RangeLimit(-BigDecimal.valueOf(10).pow(128), BigDecimal.valueOf(10).pow(126))
  )
  override val messageWaitTimeLimit: Limit[RangeLimit[Long]] = LimitedValue(RangeLimit(0L, 20L))
  override val maxMessageLength: Limit[Int] = LimitedValue(262144)
}
case object RelaxedSQSLimits extends Limits {
  override val queueNameLengthLimit: Limit[Int] = NoLimit
  override val batchSizeLimit: Limit[Int] = NoLimit
  override val numberOfMessagesLimit: Limit[RangeLimit[Int]] = NoLimit
  override val nonEmptyAttributesLimit: Limit[Boolean] = NoLimit
  override val bodyValidCharactersLimit: Limit[List[RangeLimit[Int]]] = NoLimit
  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]
case class LimitedValue[A](value: A) extends Limit[A]
case object NoLimit extends Limit[Nothing]

case class RangeLimit[A](from: A, to: A)(implicit ord: Ordering[A]) {
  def isBetween(value: A): Boolean = ord.gteq(value, from) && ord.lteq(value, to)
}

object Limits {

  def verifyBatchSize(batchSize: Int, limits: Limits): Either[String, Unit] =
    validateWhenLimitAvailable(limits.batchSizeLimit)(
      limit => batchSize <= limit,
      "Too many entries in batch request"
    )

  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
  ): Either[String, Unit] =
    validateWhenLimitAvailable(limits.numberOfMessagesLimit)(
      limit => limit.isBetween(numberOfMessagesFromParameters),
      "ReadCountOutOfRange"
    )

  def verifyMessageStringAttribute(
      attributeName: String,
      attributeValue: String,
      limits: Limits
  ): Either[String, Unit] =
    for {
      _ <- validateWhenLimitAvailable(limits.nonEmptyAttributesLimit)(
        shouldValidate => if (shouldValidate) attributeValue.nonEmpty else true,
        s"Attribute '$attributeName' must contain a non-empty value of type 'String'"
      )
      _ <- validateCharactersAndLength(attributeValue, limits)
    } yield ()

  def verifyMessageBody(body: String, limits: Limits): Either[String, Unit] =
    for {
      _ <- validateWhenLimitAvailable(limits.nonEmptyAttributesLimit)(
        shouldValidate => if (shouldValidate) body.nonEmpty else true,
        "The request must contain the parameter MessageBody."
      )
      _ <- validateCharactersAndLength(body, limits)
    } yield ()

  private def validateCharactersAndLength(stringValue: String, limits: Limits): Either[String, Unit] =
    for {
      _ <- validateWhenLimitAvailable(limits.bodyValidCharactersLimit)(
        rangeLimits =>
          stringValue
            .codePoints()
            .iterator
            .asScala
            .forall(codePoint => rangeLimits.exists(range => range.isBetween(codePoint))),
        "InvalidMessageContents"
      )
      _ <- verifyMessageLength(stringValue.length, limits)
    } yield ()

  def verifyMessageNumberAttribute(
      stringNumberValue: String,
      attributeName: String,
      limits: Limits
  ): Either[String, Unit] = {
    for {
      _ <- validateWhenLimitAvailable(limits.nonEmptyAttributesLimit)(
        shouldValidate => if (shouldValidate) stringNumberValue.nonEmpty else true,
        s"Attribute '$attributeName' must contain a non-empty value of type 'Number'"
      )
      _ <- validateWhenLimitAvailable(limits.numberAttributeValueLimit)(
        limit => Try(BigDecimal(stringNumberValue)).toOption.exists(value => limit.isBetween(value)),
        s"Number attribute value $stringNumberValue should be in range (-10**128..10**126)"
      )
    } yield ()
  }

  def verifyMessageWaitTime(messageWaitTime: Long, limits: Limits): Either[String, Unit] = {
    for {
      _ <- if (messageWaitTime < 0) Left("InvalidParameterValue") else Right(())
      _ <- validateWhenLimitAvailable(limits.messageWaitTimeLimit)(
        limit => limit.isBetween(messageWaitTime),
        "InvalidParameterValue"
      )
    } yield ()
  }

  def verifyMessageLength(messageLength: Int, limits: Limits): Either[String, Unit] = {
    validateWhenLimitAvailable(limits.maxMessageLength)(
      limit => messageLength <= limit,
      "MessageTooLong"
    )
  }

  val InvalidQueueNameError = "Can only include alphanumeric characters, hyphens, or underscores."
  val InvalidFifoQueueNameError =
    "The name of a FIFO queue can only include alphanumeric characters, hyphens, or underscores, must end with .fifo suffix."

  def verifyQueueName(queueName: String, isFifo: Boolean, limits: Limits): Either[String, Unit] = {
    for {
      _ <-
        if (isFifo && !queueName.matches("[\\p{Alnum}_-]+\\.fifo"))
          Left(InvalidFifoQueueNameError)
        else Right(())
      _ <-
        if (!isFifo && !queueName.matches("[\\p{Alnum}_-]+"))
          Left(InvalidQueueNameError)
        else Right(())
      _ <- validateWhenLimitAvailable(limits.queueNameLengthLimit)(
        limit => queueName.length <= limit,
        limits.queueNameLengthLimit match {
          case LimitedValue(v) => s"The name of a queue must not be longer than $v."
          case _               => ""
        }
      )
    } yield ()
  }

  private def validateWhenLimitAvailable[LimitValue](
      limit: Limit[LimitValue]
  )(validation: LimitValue => Boolean, error: String): Either[String, Unit] =
    limit match {
      case LimitedValue(value) => if (!validation(value)) Left(error) else Right(())
      case NoLimit             => Right(())
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy