From 3a35246d6051bed1660d1eca0328cc2d75fec3d4 Mon Sep 17 00:00:00 2001 From: Iltotore Date: Sat, 4 Nov 2023 15:44:16 +0100 Subject: [PATCH] fix: Compiler crash with Iron v2.3.0 --- .../scala/tapir/iron/TapirCodecIron.scala | 233 ++++++++++++++++++ modules/api/src/main/scala/types.scala | 2 +- 2 files changed, 234 insertions(+), 1 deletion(-) create mode 100644 modules/api/src/main/scala/tapir/iron/TapirCodecIron.scala diff --git a/modules/api/src/main/scala/tapir/iron/TapirCodecIron.scala b/modules/api/src/main/scala/tapir/iron/TapirCodecIron.scala new file mode 100644 index 0000000..5953074 --- /dev/null +++ b/modules/api/src/main/scala/tapir/iron/TapirCodecIron.scala @@ -0,0 +1,233 @@ +package tapir.iron +import io.github.iltotore.iron.Constraint +import io.github.iltotore.iron.:| +import io.github.iltotore.iron.refineEither +import io.github.iltotore.iron.refineOption +import io.github.iltotore.iron.RefinedTypeOps +import io.github.iltotore.iron.constraint.any.* +import io.github.iltotore.iron.constraint.string.* +import io.github.iltotore.iron.constraint.collection.* +import io.github.iltotore.iron.constraint.numeric.* + +import sttp.tapir.Codec +import sttp.tapir.CodecFormat +import sttp.tapir.DecodeResult +import sttp.tapir.Schema +import sttp.tapir.Validator +import sttp.tapir.Validator.Primitive +import sttp.tapir.ValidationError +import sttp.tapir.ValidationResult +import sttp.tapir.typelevel.IntersectionTypeMirror +import sttp.tapir.typelevel.UnionTypeMirror + +import scala.compiletime.* +import scala.util.NotGiven + +trait TapirCodecIron extends DescriptionWitness with LowPriorityValidatorForPredicate { + + inline given ironTypeSchema[Value, Predicate](using + inline vSchema: Schema[Value], + inline constraint: Constraint[Value, Predicate], + inline validatorTranslation: ValidatorForPredicate[Value, Predicate] + ): Schema[Value :| Predicate] = + vSchema.validate(validatorTranslation.validator).map[Value :| Predicate](v => v.refineOption[Predicate])(identity) + + inline given ironTypeCodec[Representation, Value, Predicate, CF <: CodecFormat](using + inline tm: Codec[Representation, Value, CF], + inline constraint: Constraint[Value, Predicate], + inline validatorTranslation: ValidatorForPredicate[Value, Predicate] + ): Codec[Representation, Value :| Predicate, CF] = + summon[Codec[Representation, Value, CF]] + .validate(validatorTranslation.validator) + .mapDecode { (v: Value) => + v.refineEither[Predicate] match { + case Right(refined) => DecodeResult.Value[Value :| Predicate](refined) + case Left(errorMessage) => + DecodeResult.InvalidValue(validatorTranslation.makeErrors(v, constraint.message)) + } + }(identity) + + given refinedTypeSchema[T](using m: RefinedTypeOps.Mirror[T], ev: Schema[m.IronType]): Schema[T] = + ev.asInstanceOf[Schema[T]] + + given refinedTypeCodec[R, T, CF <: CodecFormat](using m: RefinedTypeOps.Mirror[T], ev: Codec[R, m.IronType, CF]): Codec[R, T, CF] = + ev.asInstanceOf[Codec[R, T, CF]] + + inline given (using + inline vSchema: Schema[String], + inline constraint: Constraint[String, ValidUUID], + inline validatorTranslation: ValidatorForPredicate[String, ValidUUID] + ): Schema[String :| ValidUUID] = + ironTypeSchema[String, ValidUUID].format("uuid") + + inline given PrimitiveValidatorForPredicate[String, Not[Empty]] = + ValidatorForPredicate.fromPrimitiveValidator[String, Not[Empty]](Validator.minLength(1)) + + 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 + witness: ValueOf[NM] + ): PrimitiveValidatorForPredicate[T, MaxLength[NM]] = + ValidatorForPredicate.fromPrimitiveValidator(Validator.maxLength[T](witness.value)) + + inline given validatorForMinSizeOnString[T <: String, NM <: Int](using + witness: ValueOf[NM] + ): PrimitiveValidatorForPredicate[T, MinLength[NM]] = + ValidatorForPredicate.fromPrimitiveValidator(Validator.minLength[T](witness.value)) + + inline given validatorForLess[N: Numeric, NM <: N](using witness: ValueOf[NM]): PrimitiveValidatorForPredicate[N, Less[NM]] = + ValidatorForPredicate.fromPrimitiveValidator(Validator.max(witness.value, exclusive = true)) + + inline given validatorForLessEqual[N: Numeric, NM <: N](using witness: ValueOf[NM]): PrimitiveValidatorForPredicate[N, LessEqual[NM]] = + ValidatorForPredicate.fromPrimitiveValidator(Validator.max(witness.value, exclusive = false)) + + inline given validatorForGreater[N: Numeric, NM <: N](using witness: ValueOf[NM]): PrimitiveValidatorForPredicate[N, Greater[NM]] = + ValidatorForPredicate.fromPrimitiveValidator(Validator.min(witness.value, exclusive = true)) + + inline given validatorForGreaterEqual[N: Numeric, NM <: N](using + witness: ValueOf[NM] + ): PrimitiveValidatorForPredicate[N, GreaterEqual[NM]] = + ValidatorForPredicate.fromPrimitiveValidator(Validator.min(witness.value, exclusive = false)) + + inline given validatorForStrictEqual[N: Numeric, NM <: N](using + witness: ValueOf[NM] + ): PrimitiveValidatorForPredicate[N, StrictEqual[NM]] = + ValidatorForPredicate.fromPrimitiveValidator( + Validator.enumeration[N](List(witness.value)) + ) + + private inline def summonValidators[N, A <: Tuple]: List[ValidatorForPredicate[N, Any]] = { + inline erasedValue[A] match + case _: EmptyTuple => Nil + case _: (head *: tail) => + summonInline[ValidatorForPredicate[N, head]] + .asInstanceOf[ValidatorForPredicate[N, Any]] :: summonValidators[N, tail] + } + + inline given validatorForAnd[N, Predicates](using mirror: IntersectionTypeMirror[Predicates]): ValidatorForPredicate[N, Predicates] = + new ValidatorForPredicate[N, Predicates] { + + val intersectionConstraint = new Constraint.IntersectionConstraint[N, Predicates] + val validatorsForPredicates: List[ValidatorForPredicate[N, Any]] = summonValidators[N, mirror.ElementTypes] + + override def validator: Validator[N] = Validator.all(validatorsForPredicates.map(_.validator): _*) + + override def makeErrors(value: N, errorMessage: String): List[ValidationError[_]] = + if (!intersectionConstraint.test(value)) + List( + ValidationError[N]( + Validator.Custom(_ => + ValidationResult.Invalid(intersectionConstraint.message) // at this point the validator is already failed anyway + ), + value + ) + ) + else Nil + + } + + inline given validatorForOr[N, Predicates](using mirror: UnionTypeMirror[Predicates]): ValidatorForPredicate[N, Predicates] = + new ValidatorForPredicate[N, Predicates] { + + val unionConstraint = new Constraint.UnionConstraint[N, Predicates] + val validatorsForPredicates: List[ValidatorForPredicate[N, Any]] = summonValidators[N, mirror.ElementTypes] + + override def validator: Validator[N] = Validator.any(validatorsForPredicates.map(_.validator): _*) + + override def makeErrors(value: N, errorMessage: String): List[ValidationError[_]] = + if (!unionConstraint.test(value)) + List( + ValidationError[N]( + Validator.Custom(_ => + ValidationResult.Invalid(unionConstraint.message) // at this point the validator is already failed anyway + ), + value + ) + ) + else Nil + + } + + inline given validatorForDescribedAnd[N, P](using + id: IsDescription[P], + mirror: IntersectionTypeMirror[id.Predicate] + ): ValidatorForPredicate[N, P] = + validatorForAnd[N, id.Predicate].asInstanceOf[ValidatorForPredicate[N, P]] + + inline given validatorForDescribedOr[N, P](using + id: IsDescription[P], + mirror: UnionTypeMirror[id.Predicate] + ): ValidatorForPredicate[N, P] = + validatorForOr[N, id.Predicate].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]] + +} + +private[iron] trait ValidatorForPredicate[Value, Predicate] { + def validator: Validator[Value] + def makeErrors(value: Value, errorMessage: String): List[ValidationError[_]] +} + +private[iron] trait PrimitiveValidatorForPredicate[Value, Predicate] extends ValidatorForPredicate[Value, Predicate] { + def validator: Validator.Primitive[Value] + def makeErrors(value: Value, errorMessage: String): List[ValidationError[_]] +} + +private[iron] object ValidatorForPredicate { + def fromPrimitiveValidator[Value, Predicate]( + primitiveValidator: Validator.Primitive[Value] + ): PrimitiveValidatorForPredicate[Value, Predicate] = + new PrimitiveValidatorForPredicate[Value, Predicate] { + override def validator: Validator.Primitive[Value] = primitiveValidator + override def makeErrors(value: Value, errorMessage: String): List[ValidationError[_]] = + List(ValidationError[Value](primitiveValidator, value)) + } +} + +private[iron] trait LowPriorityValidatorForPredicate extends DescriptionWitness { + + inline given [Value, Predicate](using + inline constraint: Constraint[Value, Predicate], + id: IsDescription[Predicate], + notIntersection: NotGiven[IntersectionTypeMirror[id.Predicate]], + notUnion: NotGiven[UnionTypeMirror[id.Predicate]] + ): ValidatorForPredicate[Value, Predicate] = + new ValidatorForPredicate[Value, Predicate] { + + override val validator: Validator.Custom[Value] = Validator.Custom { v => + if (constraint.test(v)) ValidationResult.Valid + else ValidationResult.Invalid(constraint.message) + } + + override def makeErrors(value: Value, errorMessage: String): List[ValidationError[_]] = + List(ValidationError[Value](validator, value, Nil, Some(errorMessage))) + } +} + +private[iron] trait DescriptionWitness { + + trait IsDescription[A] { + type Predicate + type Description + } + + class DescriptionTypeMirrorImpl[A, P, D <: String] extends IsDescription[A] { + override type Predicate = P + override type Description = D + } + object IsDescription { + transparent inline given derived[A]: IsDescription[A] = + inline erasedValue[A] match { + case _: DescribedAs[p, d] => new DescriptionTypeMirrorImpl[A, p, d] + } + } + +} \ No newline at end of file diff --git a/modules/api/src/main/scala/types.scala b/modules/api/src/main/scala/types.scala index aaa6c2a..6e7b931 100644 --- a/modules/api/src/main/scala/types.scala +++ b/modules/api/src/main/scala/types.scala @@ -1,7 +1,7 @@ import io.github.iltotore.iron.* import io.github.iltotore.iron.constraint.numeric.* import sttp.tapir.Codec.PlainCodec -import sttp.tapir.codec.iron.TapirCodecIron +import tapir.iron.TapirCodecIron opaque type Version = Int :| GreaterEqual[0]