Skip to content

Commit

Permalink
Fixes in iron: union, intersection, Not[Empty] and other cases (#3858)
Browse files Browse the repository at this point in the history
  • Loading branch information
kciesielski authored Jun 20, 2024
1 parent 3a68449 commit b355571
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,12 @@ object IntersectionTypeMirror {
def rec(tpe: TypeRepr): TypeRepr = {
tpe.dealias match
case AndType(left, right) => concatTypes(rec(left), rec(right))
case t => prependTypes(t, TypeRepr.of[EmptyTuple])
case t =>
// Intentionally using `tpe` instead of `t`. Dealiased representation `t` "loses" information
// about the original type from the intersection. For example, an Iron predicate `MinLength[N]`
// would be dealiased to `DescribedAs[Length[GreaterEqual[N]], _]`.
// Then, a given `ValidatorForPredicate[T, MinLength[N]]` would not be used in implicit resolution.
prependTypes(tpe, TypeRepr.of[EmptyTuple])
}
val tupled =
TypeRepr.of[A].dealias match {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,12 +84,12 @@ trait TapirCodecIron extends DescriptionWitness with LowPriorityValidatorForPred
inline given validatorForMatchesRegexpString[S <: String](using witness: ValueOf[S]): PrimitiveValidatorForPredicate[String, Match[S]] =
ValidatorForPredicate.fromPrimitiveValidator(Validator.pattern[String](witness.value))

inline given validatorForMaxSizeOnString[T <: String, NM <: Int](using
inline given validatorForMaxLengthOnString[T <: String, NM <: Int](using
witness: ValueOf[NM]
): PrimitiveValidatorForPredicate[T, MaxLength[NM]] =
ValidatorForPredicate.fromPrimitiveValidator(Validator.maxLength[T](witness.value))

inline given validatorForMinSizeOnString[T <: String, NM <: Int](using
inline given validatorForMinLengthOnString[T <: String, NM <: Int](using
witness: ValueOf[NM]
): PrimitiveValidatorForPredicate[T, MinLength[NM]] =
ValidatorForPredicate.fromPrimitiveValidator(Validator.minLength[T](witness.value))
Expand Down Expand Up @@ -133,8 +133,8 @@ trait TapirCodecIron extends DescriptionWitness with LowPriorityValidatorForPred
case _: EmptyTuple => Nil
case _: (head *: tail) =>
val headValidator: ValidatorForPredicate[N, ?] = summonFrom {
case pv: PrimitiveValidatorForPredicate[N, head] => pv
case _ => summonInline[ValidatorForPredicate[N, head]]
case pv: PrimitiveValidatorForPredicate[N, `head`] => pv
case _ => summonInline[ValidatorForPredicate[N, head]]
}
headValidator.asInstanceOf[ValidatorForPredicate[N, Any]] :: summonValidators[N, tail]
}
Expand Down Expand Up @@ -234,14 +234,14 @@ trait TapirCodecIron extends DescriptionWitness with LowPriorityValidatorForPred
singleton: ValueOf[Num]
): ValidatorForPredicate[N, P] =
validatorForLessEqual[N, Num].asInstanceOf[ValidatorForPredicate[N, P]]

inline given validatorForDescribedPrimitive[N, P](using
id: IsDescription[P],
notUnion: NotGiven[UnionTypeMirror[id.Predicate]],
notIntersection: NotGiven[IntersectionTypeMirror[id.Predicate]],
inline validator: ValidatorForPredicate[N, id.Predicate]
): ValidatorForPredicate[N, P] =
validator.asInstanceOf[ValidatorForPredicate[N, P]]

inline validator: PrimitiveValidatorForPredicate[N, id.Predicate]
): PrimitiveValidatorForPredicate[N, P] =
validator.asInstanceOf[PrimitiveValidatorForPredicate[N, P]]
}

private[iron] trait ValidatorForPredicate[Value, Predicate] {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,12 @@ object UnionTypeMirror {
val (c1, rec1) = rec(left)
val (c2, rec2) = rec(right)
(c1 + c2, concatTypes(rec1, rec2))
case t => (1, prependTypes(t, TypeRepr.of[EmptyTuple]))
case t =>
// Intentionally using `tpe` instead of `t`. Dealiased representation `t` "loses" information
// about the original type from the union. For example, an Iron predicate `MinLength[N]`
// would be dealiased to `DescribedAs[Length[GreaterEqual[N]], _]`.
// Then, a given `ValidatorForPredicate[T, MinLength[N]]` would not be used in implicit resolution.
(1, prependTypes(tpe, TypeRepr.of[EmptyTuple]))
}
val (size, tupled) =
TypeRepr.of[A].dealias match {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,46 @@ class TapirCodecIronTestScala3 extends AnyFlatSpec with Matchers {
}
}

"Generated schema for described Int with extra constrains" should "apply given constrains" in {
val schema = implicitly[Schema[Int :| (Positive DescribedAs "Age should be positive")]]

schema.validator should matchPattern { case Validator.Mapped(Validator.Min(0, true), _) =>
}
}

"Generated schema for described String with extra constrains" should "apply given constrains" in {
type Constraint = (Not[Empty] & Alphanumeric) DescribedAs "name should not be empty and only made of alphanumeric characters"
type VariableString = String :| Constraint
val schema = implicitly[Schema[VariableString]]

schema.validator should matchPattern {
case Validator.Mapped(Validator.All(List(Validator.MinLength(1, false), Validator.Custom(_, _))), _) =>
}
val codec = implicitly[PlainCodec[VariableString]]
codec.decode("alpha1") shouldBe a[DecodeResult.Value[_]]
codec.decode("bad!") shouldBe a[DecodeResult.InvalidValue]
codec.decode("") shouldBe a[DecodeResult.InvalidValue]
codec.decode("954") shouldBe a[DecodeResult.Value[_]]
codec.decode("spaces not allowed") shouldBe a[DecodeResult.InvalidValue]
}

"Generated schema for non empty string" should "use a MinLength validator" in {
type VariableString = String :| Not[Empty]
val schema = implicitly[Schema[VariableString]]

schema.validator should matchPattern { case Validator.Mapped(Validator.MinLength(1, false), _) =>
}
}

"Generated schema for union and intersection on string" should "use a list of tapir validations" in {
type VariableString = String :| (MinLength[33] & MaxLength[83])
val schema = implicitly[Schema[VariableString]]

schema.validator should matchPattern {
case Validator.Mapped(Validator.All(List(Validator.MinLength(33, false), Validator.MaxLength(83, false))), _) =>
}
}

"Generated codec for Less" should "use tapir Validator.max" in {
type IntConstraint = Less[3]
type LimitedInt = Int :| IntConstraint
Expand Down

0 comments on commit b355571

Please sign in to comment.