# Constraint

In Iron, a constraint consist of a type, called "dummy" type, associated with a given instance of Constraint.

See Refinement for usage.

## Operations

Usually, you can make your constraint out of existing ones. Iron provides several operators to help you to compose them.

### Union and intersection

Type union `C1 | C2` and intersection `C1 & C2` respectively act as a boolean OR/AND in Iron. For example, GreaterEqual is just a union of Greater and StrictEqual:

``````import io.github.iltotore.iron.*
import io.github.iltotore.iron.constraint.all.*

type GreaterEqual[V] = Greater[V] | StrictEqual[V]
``````
GreaterEqual.scala
``````import io.github.iltotore.iron.*
import io.github.iltotore.iron.constraint.all.*

type GreaterEqual[V] = Greater[V] | StrictEqual[V]

val x: Int :| GreaterEqual[0] = 1 //OK
val y: Int :| GreaterEqual[0] = -1 //Compile-time error: (Should be greater than 0 | Should strictly equal to 0)
``````

Same goes for intersection:

``````import io.github.iltotore.iron.*
import io.github.iltotore.iron.constraint.all.*

type GreaterEqual[V] = Greater[V] | StrictEqual[V]

type Between[Min, Max] = GreaterEqual[Min] & LessEqual[Max]
``````

### Other operations

Most constraint operators provided by Iron are "normal" constraints taking another constraint as parameter.

Here is a list of the most used operators:

• Not[C]: like a boolean "not". Negate the result of the `C` constraint.
• DescribedAs[C, V]: attach a custom description `V` to `C`.
• ForAll[C]: check if the `C` constraint passes for all elements of a collection/String
• Exists[C]: check if the `C` constraint passes for at least one element of a collection/String

## Dummy type

Usually, the dummy type is represented by a final class. Note that this class (or whatever entity you choose as a dummy) should not have constructor parameters.

``````final class Positive
``````
Positive.scala

The dummy type does nothing in itself. It is only used by the type system/implicit search to select the right Constraint.

## Constraint implementation

Each refined type `A :| C` need an implicit instance of `Constraint[A, C]` to be verified. For instance, `Int :| Positive` need a given instance of `Constraint[Int, Positive]`.

Here is how it looks:

``````final class Positive

import io.github.iltotore.iron.*

given Constraint[Int, Positive] with

override inline def test(value: Int): Boolean = value > 0

override inline def message: String = "Should be strictly positive"
``````
PositiveAndConstraint.scala

Note that you need to do this for each type. If your constraint supports multiple types (e.g numeric types), you can use a trait to reduce boilerplate:

``````final class Positive

import io.github.iltotore.iron.*

trait PositiveConstraint[A] extends Constraint[A, Positive]:
override inline def message: String = "Should be strictly positive"

given PositiveConstraint[Int] with
override inline def test(value: Int): Boolean = value > 0

given PositiveConstraint[Double] with
override inline def test(value: Double): Boolean = value > 0.0
``````

This constraint can now be used like any other:

``````final class Positive

import io.github.iltotore.iron.*

given Constraint[Int, Positive] with

override inline def test(value: Int): Boolean = value > 0

override inline def message: String = "Should be strictly positive"

val x: Int :| Positive = 1
val y: Int :| Positive = -1 //Compile-time error: Should be strictly positive
``````

## Constraint parameters

You can parameterize your constraints. Let's take the standard Greater constraint.

Constraint parameters are held by the dummy type as type parameters, not constructor parameters.

``````final class Greater[V]
``````
Greater.scala

Then, we can get the value of the passed type using `scala.compiletime.constValue`:

``````final class Greater[V]

import io.github.iltotore.iron.*
import scala.compiletime.constValue

given [V]: Constraint[Int, Greater[V]] with

override inline def test(value: Int): Boolean = value > constValue[V]

override inline def message: String = "Should be greater than " + stringValue[V]
``````
GreaterAndConstraint.scala

Note that we're using stringValue in the `message` method to get a fully inlined String value of the given type because `String#toString` is not inlined. This method is equivalent to `constValue[scala.compiletime.ops.any.ToString[V]]`.

Now testing the constraint:

``````final class Greater[V]

import io.github.iltotore.iron.*
import scala.compiletime.constValue

given [V]: Constraint[Int, Greater[V]] with

override inline def test(value: Int): Boolean = value > constValue[V]

override inline def message: String = "Should be greater than " + stringValue[V]

val x: Int :| Greater[5] = 6
val y: Int :| Greater[5] = 3 //Compile-time error: Should be greater than 5
``````

## Runtime proxy

Iron provides a proxy for `Constraint`, named `RuntimeConstraint`. It is used the same way as `Constraint`:

``````def refineRuntimeOption[A, C](value: A)(using constraint: RuntimeConstraint[A, C]): Option[A :| C] =
Option.when(constraint.test(value))(value.asInstanceOf[A :| C])

refineRuntimeOption[Int, Positive](5) //Some(5)
refineRuntimeOption[Int, Positive](-5) //None
``````

• It does not need the summoning method (here `refineOption`) to be `inline`
• It significantly lowers the generated bytecode and usually improves performances

Therefore, it is recommended to use `RuntimeConstraint` instead of `Constraint` when using the instance at runtime. For example, most of `RefinedTypeOps`'s (see New types) methods use a `RuntimeConstraint`.

It is also recommended to use `RuntimeConstraint` to derive typeclasses, especially when using a `given` with a function value.

``````trait FromString[A]:

def fromString(text: String): Either[String, A]

given [A, C](using constraint: RuntimeConstraint[A, C], instanceA: FromString[A]): FromString[A :| C] = text =>
instanceA
.fromString(text)
.filterOrElse(constraint.test(_), constraint.message)
.map(_.asInstanceOf[A :| C])
``````

Note that using a `Constraint` here (and having to our given instance `inline`) will produce a warning:

An inline given alias with a function value as right-hand side can significantly increase generated code size. You should either drop the `inline` or rewrite the given with an explicit `apply` method.

`RuntimeConstraint` is also useful when you need to reuse the same constraint. Here is an example from `RefinedTypeOps`:

``````trait RefinedTypeOps[A, C, T]:

inline def rtc: RuntimeConstraint[A, C] = ???

def option(value: A): Option[T] =
Option.when(rtc.test(value))(value.asInstanceOf[T])
``````

## Pre-defined constraints

Iron provides a set of pre-defined constraints in the io.github.iltotore.iron.constraint package. You can find them in the API documentation.

### Global constraints

Some constraints are available for all types. They are located in the io.github.iltotore.iron.constraint.any object.

Here is a list of the most used ones:

• StrictEqual: check if a value is equal to a given one.
• Not: a constraint decorator to negate another constraint. The ! alias is also available.
• DescribedAs: attach a custom description to a constraint.
• True: an always-true constraint.
• False: an always-false constraint.
• Xor: a boolean XOR between two constraints.
• In: check if a value is in a given value tuple.

### Char constraints

Some constraints are available for `Char` types. They are located in the io.github.iltotore.iron.constraint.char object.

Here is a list of the most used ones:

• Digit: check if a character is a digit.
• Letter: check if a character is a letter.
• LowerCase: check if a character is a lower case character.
• UpperCase: check if a character is an upper case character.
• Whitespace: check if a character is a whitespace character.
• Special: check if a character is a special character (i.e. not a digit nor a letter).

### Numeric constraints

Some constraints are available for numeric types. They are located in the io.github.iltotore.iron.constraint.numeric object.

Here is a list of the most used ones:

• Less: check if a value is less than a given one.
• Greater: check if a value is greater than a given one.
• LessEqual: check if a value is less than or equal to a given one.
• GreaterEqual: check if a value is greater than or equal to a given one.
• Positive: check if a value is strictly positive.
• Negative: check if a value is strictly negative.
• Positive0: check if a value is positive or zero.
• Negative0: check if a value is negative or zero.
• Interval.Closed: check if a value is in a closed interval.
• Interval.Open: check if a value is in an open interval.
• Interval.OpenClosed: check if a value is in an open-closed interval.
• Interval.ClosedOpen: check if a value is in a closed-open interval.
• Infinity: check if a value is infinite (positive or negative).
• NaN: check if a value is not a representable number.
• Multiple: check if a value is a multiple of another one.
• Divide: check if a value is a divisor of another one.
• Odd: check if a value is odd.
• Even: check if a value is even.

### Collection constraints

Some constraints are available for collections. They are located in the io.github.iltotore.iron.constraint.collection object.

Here is a list of the most used ones:

• ForAll: check if a constraint passes for all elements of a collection.
• Exists: check if a constraint passes for at least one element of a collection.
• Length: check if the collection length satisfies a given constraint.
• Empty: check if a collection is empty.
• FixedLength: check if a collection has a fixed length.
• MinLength: check if a collection has a minimum length.
• MaxLength: check if a collection has a maximum length.
• Contains: check if a collection contains a given element.
• Last: check if a collection's last element satisfies a given constraint.
• Tail: check if a collection's tail satisfies a given constraint.
• Init: check if a collection's init satisfies a given constraint.

### String constraints

Some constraints are available for `String` types. They are located in the io.github.iltotore.iron.constraint.string object. Note that, as `String` is an `Iterable[Char]`, you can use the collection constraints on `String`.

Here is a list of the most used ones:

• Blank: check if a string is blank (i.e. empty or only containing whitespaces).
• StartWith: check if a string starts with a given prefix.
• EndWith: check if a string ends with a given suffix.
• Match: check if a string matches a given regular expression.
• Alphanumeric: check if a string contains only alphanumeric characters.
• LettersLowerCase: check if all letters of a string are lower-cased letters.
• LettersUpperCase: check if all letters of a string are upper-cased letters.
• Trimmed: check if a string is trimmed (i.e. without leading and trailing whitespaces).
• ValidUUID: check if a string is a valid UUID.
• ValidURL: check if a string is a valid URL.
• SemanticVersion: check if a string is a valid semantic version as defined on semver.org.