Skip to content

Commit

Permalink
feat: Length and Contain constraints support for Array added (#280)
Browse files Browse the repository at this point in the history
Part of #279 

I have added support of Length and Contain constraints for Arrays and
update the docs of these constraints.
  • Loading branch information
orangepigment authored Dec 13, 2024
1 parent f71de9c commit 1609d0c
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 3 deletions.
37 changes: 34 additions & 3 deletions main/src/io/github/iltotore/iron/constraint/collection.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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]

Expand All @@ -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.
*/
Expand Down Expand Up @@ -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] =
Expand All @@ -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:
Expand All @@ -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.*
Expand All @@ -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]]:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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]]
Expand All @@ -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]]
Expand All @@ -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]
Expand All @@ -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]]
Expand All @@ -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]]
Expand All @@ -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]]
Expand Down

0 comments on commit 1609d0c

Please sign in to comment.