From 1609d0c7ce290aead63b48419bef8bcf925456df Mon Sep 17 00:00:00 2001 From: orangepigment <88874983+orangepigment@users.noreply.github.com> Date: Fri, 13 Dec 2024 13:04:04 +0300 Subject: [PATCH] feat: Length and Contain constraints support for Array added (#280) Part of #279 I have added support of Length and Contain constraints for Arrays and update the docs of these constraints. --- .../iltotore/iron/constraint/collection.scala | 37 +++++++++++++++++-- .../iron/testing/CollectionSuite.scala | 25 +++++++++++++ 2 files changed, 59 insertions(+), 3 deletions(-) diff --git a/main/src/io/github/iltotore/iron/constraint/collection.scala b/main/src/io/github/iltotore/iron/constraint/collection.scala index 552d5a2..7ae8f8e 100644 --- a/main/src/io/github/iltotore/iron/constraint/collection.scala +++ b/main/src/io/github/iltotore/iron/constraint/collection.scala @@ -24,21 +24,21 @@ object collection: final class Length[C] /** - * Tests minimum length. Supports [[Iterable]] and [[String]] by default. + * Tests minimum length. Supports [[Iterable]], [[String]] and [[Array]] by default. * * @tparam V the minimum length of the tested input */ type MinLength[V <: Int] = DescribedAs[Length[GreaterEqual[V]], "Should have a minimum length of " + V] /** - * Tests maximum length. Supports [[Iterable]] and [[String]] by default. + * Tests maximum length. Supports [[Iterable]], [[String]] and [[Array]] by default. * * @tparam V the maximum length of the tested input */ type MaxLength[V <: Int] = DescribedAs[Length[LessEqual[V]], "Should have a maximum length of " + V] /** - * Tests exact length. Supports [[Iterable]] and [[String]] by default. + * Tests exact length. Supports [[Iterable]], [[String]] and [[Array]] by default. */ type FixedLength[V <: Int] = DescribedAs[Length[StrictEqual[V]], "Should have an exact length of " + V] @@ -49,6 +49,7 @@ object collection: /** * Tests if the given collection contains a specific value. + * Supports [[Iterable]], [[String]] and [[Array]] * * @tparam V the value the input must contain. */ @@ -111,6 +112,14 @@ object collection: inline given lengthString[C, Impl <: Constraint[Int, C]](using inline impl: Impl): LengthString[C, Impl] = new LengthString + class LengthArray[A, C, Impl <: Constraint[Int, C]](using Impl) extends Constraint[Array[A], Length[C]]: + + override inline def test(inline value: Array[A]): Boolean = ${ checkArray('value, '{ summonInline[Impl] }) } + + override inline def message: String = "Length: (" + summonInline[Impl].message + ")" + + inline given lengthArray[A, C, Impl <: Constraint[Int, C]](using inline impl: Impl): LengthArray[A, C, Impl] = new LengthArray + private def checkIterable[I <: Iterable[?]: Type, C, Impl <: Constraint[Int, C]](expr: Expr[I], constraintExpr: Expr[Impl])(using Quotes ): Expr[Boolean] = @@ -129,6 +138,14 @@ object collection: case Right(value) => applyConstraint(Expr(value.length), constraintExpr) case _ => applyConstraint('{ $expr.length }, constraintExpr) + private def checkArray[C, Impl <: Constraint[Int, C]](expr: Expr[Array[?]], constraintExpr: Expr[Impl])(using Quotes): Expr[Boolean] = + val rflUtil = reflectUtil + import rflUtil.* + + expr.decode match + case Right(value) => applyConstraint(Expr(value.length), constraintExpr) + case _ => applyConstraint('{ $expr.length }, constraintExpr) + given [C1, C2](using C1 ==> C2): (Length[C1] ==> Length[C2]) = Implication() object Contain: @@ -144,6 +161,12 @@ object collection: override inline def message: String = "Should contain the string " + constValue[V] + inline given [A, V <: A]: Constraint[Array[A], Contain[V]] with + + override inline def test(inline value: Array[A]): Boolean = ${ checkArray('value, '{ constValue[V] }) } + + override inline def message: String = "Should contain the value " + constValue[V] + private def checkIterable[I <: Iterable[?]: Type, V: Type](expr: Expr[I], partExpr: Expr[V])(using Quotes): Expr[Boolean] = val rflUtil = reflectUtil import rflUtil.* @@ -160,6 +183,14 @@ object collection: case (Right(value), Right(part)) => Expr(value.contains(part)) case _ => '{ ${ expr }.contains($partExpr) } + private def checkArray[V: Type](expr: Expr[Array[V]], partExpr: Expr[V])(using Quotes): Expr[Boolean] = + val rflUtil = reflectUtil + import rflUtil.* + + (expr.decode, partExpr.decode) match + case (Right(value), Right(part)) => Expr(value.contains(part)) + case _ => '{ ${ expr }.contains($partExpr) } + object ForAll: class ForAllIterable[A, I <: Iterable[A], C, Impl <: Constraint[A, C]](using Impl) extends Constraint[I, ForAll[C]]: diff --git a/main/test/src/io/github/iltotore/iron/testing/CollectionSuite.scala b/main/test/src/io/github/iltotore/iron/testing/CollectionSuite.scala index 45e510f..63d5f35 100644 --- a/main/test/src/io/github/iltotore/iron/testing/CollectionSuite.scala +++ b/main/test/src/io/github/iltotore/iron/testing/CollectionSuite.scala @@ -26,6 +26,10 @@ object CollectionSuite extends TestSuite: test - "1234".assertRefine[Length[Greater[3]]] test - "123".assertNotRefine[Length[Greater[3]]] + test("array"): + test - Array(1, 2, 3, 4).assertRefine[Length[Greater[3]]] + test - Array(1, 2, 3).assertNotRefine[Length[Greater[3]]] + test("minLength"): test("iterable"): test - List(1, 2, 3, 4).assertRefine[MinLength[4]] @@ -35,6 +39,10 @@ object CollectionSuite extends TestSuite: test - "abc".assertNotRefine[MinLength[4]] test - "abcd".assertRefine[MinLength[4]] + test("array"): + test - Array(1, 2, 3, 4).assertRefine[MinLength[4]] + test - Array(1, 2, 3).assertNotRefine[MinLength[4]] + test("maxLength"): test("iterable"): test - List(1, 2, 3).assertRefine[MaxLength[3]] @@ -44,6 +52,10 @@ object CollectionSuite extends TestSuite: test - "abc".assertRefine[MaxLength[3]] test - "abcd".assertNotRefine[MaxLength[3]] + test("array"): + test - Array(1, 2, 3).assertRefine[MaxLength[3]] + test - Array(1, 2, 3, 4).assertNotRefine[MaxLength[3]] + test("empty"): test("iterable"): test - Nil.assertRefine[Empty] @@ -53,6 +65,10 @@ object CollectionSuite extends TestSuite: test - "".assertRefine[Empty] test - "abc".assertNotRefine[Empty] + test("array"): + test - Array.emptyIntArray.assertRefine[Empty] + test - Array(1, 2, 3).assertNotRefine[Empty] + test("fixedLength"): test("iterable"): test - List(1, 2, 3).assertRefine[FixedLength[3]] @@ -64,6 +80,11 @@ object CollectionSuite extends TestSuite: test - "ab".assertNotRefine[FixedLength[3]] test - "abcd".assertNotRefine[FixedLength[3]] + test("array"): + test - Array(1, 2, 3).assertRefine[FixedLength[3]] + test - Array(1, 2).assertNotRefine[FixedLength[3]] + test - Array(1, 2, 3, 4).assertNotRefine[FixedLength[3]] + test("contain"): test("iterable"): test - List(1, 2, 3).assertRefine[Contain[3]] @@ -73,6 +94,10 @@ object CollectionSuite extends TestSuite: test - "abc".assertRefine[Contain["c"]] test - "abd".assertNotRefine[Contain["c"]] + test("array"): + test - Array(1, 2, 3).assertRefine[Contain[3]] + test - Array(1, 2, 4).assertNotRefine[Contain[3]] + test("forAll"): test("iterable"): test - Nil.assertRefine[ForAll[IsA]]