diff --git a/core/src/main/scala/cats/xml/validator/Validator.scala b/core/src/main/scala/cats/xml/validator/Validator.scala index ea6fd1a..ba2e5c5 100644 --- a/core/src/main/scala/cats/xml/validator/Validator.scala +++ b/core/src/main/scala/cats/xml/validator/Validator.scala @@ -6,6 +6,8 @@ import cats.kernel.Monoid import cats.{Contravariant, Eq, Show} import cats.xml.validator.Validator.must +import scala.util.matching.Regex + trait Validator[T] { $this => def apply(t: T): Validator.Result[T] @@ -129,55 +131,65 @@ private[validator] sealed trait ValidatorBuilders { .contramap[String](_.length) .rewordError(str => s"Length of '$str', expected $expected but is ${str.length}.") - def length(expected: Int): Validator[String] = - max[Int](expected) + def maxLength(maxLen: Int): Validator[String] = + max[Int](maxLen) .contramap[String](_.length) - .rewordError(str => s"Length of '$str', expected $expected but is ${str.length}.") + .rewordError(str => s"Length of '$str' expected to be <= $maxLen but is ${str.length}.") - def regex(regex: String): Validator[String] = + def minLength(minLen: Int): Validator[String] = + min[Int](minLen) + .contramap[String](_.length) + .rewordError(str => s"Length of '$str' expected to be >= $minLen but is ${str.length}.") + + def regex(regex: Regex): Validator[String] = must[String](str => s"String '$str' doesn't match regex `$regex`.")( - _.matches(regex) + regex.matches(_) ) // ------------- collections ------------- - def isEmpty[T]: Validator[Seq[T]] = - must[Seq[T]](seq => s"Seq${seqToStr(seq)} is not empty.")( - _.isEmpty + def isEmpty[F[X] <: IterableOnce[X]]: Validator[F[Any]] = + must[F[Any]](seq => s"${iterableToStr(seq)} is not empty.")( + _.iterator.isEmpty ) - def nonEmpty[T]: Validator[Seq[T]] = - must[Seq[T]](seq => s"Seq${seqToStr(seq)} is empty.")( - _.nonEmpty + def nonEmpty[F[X] <: IterableOnce[X]]: Validator[F[Any]] = + must[F[Any]](seq => s"${iterableToStr(seq)} is empty.")( + _.iterator.nonEmpty ) - def maxSize[T](maxSize: Int): Validator[Seq[T]] = - must[Seq[T]](seq => s"Seq${seqToStr(seq)} size must be <= $maxSize")( - _.size <= maxSize + def maxSize[F[X] <: IterableOnce[X]](maxSize: Int): Validator[F[Any]] = + must[F[Any]](seq => s"${iterableToStr(seq)} size must be <= $maxSize")( + _.iterator.size <= maxSize ) - def minSize[T](minSize: Int): Validator[Seq[T]] = - must[Seq[T]](seq => s"Seq${seqToStr(seq)} size must be >= $minSize")( - _.size >= minSize + def minSize[F[X] <: IterableOnce[X]](minSize: Int): Validator[F[Any]] = + must[F[Any]](seq => s"${iterableToStr(seq)} size must be >= $minSize")( + _.iterator.size >= minSize ) // ------------- cats-collections ------------- def maxSizeNel[T: Show](maxSize: Int): Validator[NonEmptyList[T]] = - must[NonEmptyList[T]](seq => s"NonEmptyList${seqToStr(seq.toList)} size must be <= $maxSize")( + must[NonEmptyList[T]](seq => + s"NonEmptyList${iterableToStr(seq.toList)} size must be <= $maxSize" + )( _.size <= maxSize ) def minSizeNel[T: Show](minSize: Int): Validator[NonEmptyList[T]] = - must[NonEmptyList[T]](seq => s"NonEmptyList${seqToStr(seq.toList)} size must be >= $minSize")( + must[NonEmptyList[T]](seq => + s"NonEmptyList${iterableToStr(seq.toList)} size must be >= $minSize" + )( _.size >= minSize ) - private def seqToStr[T](seq: Seq[T], limit: Int = 10)(implicit - s: Show[T] = Show.fromToString[T] - ): String = - if (seq.size >= limit) - seq.mkString_("[", ", ", "]") + private def iterableToStr[T](itOnce: IterableOnce[T], limit: Int = 10)(implicit + s: Show[T] = Show.fromToString[T] + ): String = { + if (itOnce.iterator.size <= limit) + itOnce.iterator.map(_.show).mkString("[", ", ", "]") else - seq.take(limit).mkString_("[", ", ", "...]") + itOnce.iterator.take(limit).map(_.show).mkString("[", ", ", ",...]") + } } private[xml] trait ValidatorInstances { diff --git a/core/src/test/scala/cats/xml/validator/ValidatorSuite.scala b/core/src/test/scala/cats/xml/validator/ValidatorSuite.scala index 1585192..82ed896 100644 --- a/core/src/test/scala/cats/xml/validator/ValidatorSuite.scala +++ b/core/src/test/scala/cats/xml/validator/ValidatorSuite.scala @@ -353,4 +353,153 @@ class ValidatorInstancesSuite extends munit.ScalaCheckSuite { expected = Invalid(NonEmptyList.one("Length of '123456', expected 5 but is 6.")) ) } + + test("Validator.maxLength") { + + val validator = Validator.maxLength(5) + assertEquals( + obtained = validator.apply("12345"), + expected = Valid("12345") + ) + + assertEquals( + obtained = validator.apply("1234"), + expected = Valid("1234") + ) + + assertEquals( + obtained = validator.apply("123456"), + expected = Invalid(NonEmptyList.one("Length of '123456' expected to be <= 5 but is 6.")) + ) + } + + test("Validator.minLength") { + + val validator = Validator.minLength(5) + assertEquals( + obtained = validator.apply("12345"), + expected = Valid("12345") + ) + + assertEquals( + obtained = validator.apply("123456"), + expected = Valid("123456") + ) + + assertEquals( + obtained = validator.apply("1234"), + expected = Invalid(NonEmptyList.one("Length of '1234' expected to be >= 5 but is 4.")) + ) + } + + test("Validator.regex") { + + val validator = Validator.regex("""(\w+)@([\w\.]+)""".r) + assertEquals( + obtained = validator.apply("mimmo@gmail.com"), + expected = Valid("mimmo@gmail.com") + ) + + assertEquals( + obtained = validator.apply("mimmo_gmail.com"), + expected = Invalid( + NonEmptyList.one("String 'mimmo_gmail.com' doesn't match regex `(\\w+)@([\\w\\.]+)`.") + ) + ) + } + + // collections + test("Validator.isEmpty") { + val validatorSeq: Validator[Seq[Any]] = Validator.isEmpty[Seq] + val validatorOption: Validator[Option[Any]] = Validator.isEmpty[Option] + + // seq + assertEquals( + obtained = validatorSeq.apply(Nil), + expected = Valid(Nil) + ) + + assertEquals( + obtained = validatorSeq.apply(Seq(1, 2, 3)), + expected = Invalid(NonEmptyList.one("[1, 2, 3] is not empty.")) + ) + + // option + assertEquals( + obtained = validatorOption.apply(None), + expected = Valid(None) + ) + + assertEquals( + obtained = validatorOption.apply(Some(1)), + expected = Invalid(NonEmptyList.one("[1] is not empty.")) + ) + } + + test("Validator.nonEmpty") { + val validatorSeq: Validator[Seq[Any]] = Validator.nonEmpty[Seq] + val validatorOption: Validator[Option[Any]] = Validator.nonEmpty[Option] + + // seq + assertEquals( + obtained = validatorSeq.apply(Seq(1, 2, 3)), + expected = Valid(Seq(1, 2, 3)) + ) + + assertEquals( + obtained = validatorSeq.apply(Nil), + expected = Invalid(NonEmptyList.one("[] is empty.")) + ) + + // option + assertEquals( + obtained = validatorOption.apply(Some(1)), + expected = Valid(Some(1)) + ) + + assertEquals( + obtained = validatorOption.apply(None), + expected = Invalid(NonEmptyList.one("[] is empty.")) + ) + } + + test("Validator.maxSize") { + val validatorSeq: Validator[Seq[Any]] = Validator.maxSize(2) + + // seq + assertEquals( + obtained = validatorSeq.apply(Seq(1, 2)), + expected = Valid(Seq(1, 2)) + ) + + assertEquals( + obtained = validatorSeq.apply(Nil), + expected = Valid(Nil) + ) + + assertEquals( + obtained = validatorSeq.apply(Seq(1, 2, 3)), + expected = Invalid(NonEmptyList.one("[1, 2, 3] size must be <= 2")) + ) + } + + test("Validator.minSize") { + val validatorSeq: Validator[Seq[Any]] = Validator.minSize(2) + + // seq + assertEquals( + obtained = validatorSeq.apply(Seq(1, 2)), + expected = Valid(Seq(1, 2)) + ) + + assertEquals( + obtained = validatorSeq.apply(Seq(1, 2, 3)), + expected = Valid(Seq(1, 2, 3)) + ) + + assertEquals( + obtained = validatorSeq.apply(Nil), + expected = Invalid(NonEmptyList.one("[] size must be >= 2")) + ) + } }