-
Hi 👋 Thank you for this amazing library. I love to use your app for my I am trying the following in my app type NonEmptyStringIron = String :| Not[Blank]
opaque type SomeId = NonEmptyStringIron I am using Tapir. It is an endpoint web library that has an integration with Refined type. Unfortunately, It seems they don't have a support for Iron yet. The link has implicit val validatorForNonEmptyString: PrimitiveValidatorForPredicate[String, NonEmpty] =
ValidatorForPredicate.fromPrimitiveValidator[String, NonEmpty](Validator.minLength(1)) That requires the following code (it's on the page) package sttp.tapir.codec.refined
import eu.timepit.refined.api.{Refined, Validate}
import eu.timepit.refined.boolean.{And, Or}
import eu.timepit.refined.collection.{MaxSize, MinSize, NonEmpty}
import eu.timepit.refined.internal.WitnessAs
import eu.timepit.refined.numeric.{Greater, GreaterEqual, Less, LessEqual}
import eu.timepit.refined.refineV
import eu.timepit.refined.string.{MatchesRegex, Uuid}
import sttp.tapir._
trait TapirCodecRefined extends LowPriorityValidatorForPredicate {
implicit def refinedTapirSchema[V, P](implicit
vSchema: Schema[V],
refinedValidator: Validate[V, P],
refinedValidatorTranslation: ValidatorForPredicate[V, P]
): Schema[V Refined P] =
vSchema.validate(refinedValidatorTranslation.validator).map[V Refined P](v => refineV[P](v).toOption)(_.value)
implicit def codecForRefined[R, V, P, CF <: CodecFormat](implicit
tm: Codec[R, V, CF],
refinedValidator: Validate[V, P],
refinedValidatorTranslation: ValidatorForPredicate[V, P]
): Codec[R, V Refined P, CF] = {
implicitly[Codec[R, V, CF]]
.validate(
refinedValidatorTranslation.validator
) // in reality if this validator has to fail, it will fail before in mapDecode while trying to construct refined type
.mapDecode { (v: V) =>
refineV[P](v) match {
case Right(refined) => DecodeResult.Value(refined)
case Left(errorMessage) =>
DecodeResult.InvalidValue(refinedValidatorTranslation.validationErrors(v, errorMessage))
}
}(_.value)
}
trait ValidatorForPredicate[V, P] {
def validator: Validator[V]
def validationErrors(value: V, refinedErrorMessage: String): List[ValidationError[_]]
}
trait PrimitiveValidatorForPredicate[V, P] extends ValidatorForPredicate[V, P] {
def validator: Validator.Primitive[V]
def validationErrors(value: V, refinedErrorMessage: String): List[ValidationError[_]]
}
object ValidatorForPredicate {
def fromPrimitiveValidator[V, P](primitiveValidator: Validator.Primitive[V]): PrimitiveValidatorForPredicate[V, P] =
new PrimitiveValidatorForPredicate[V, P] {
override def validator: Validator.Primitive[V] = primitiveValidator
override def validationErrors(value: V, refinedErrorMessage: String): List[ValidationError[_]] =
List(ValidationError[V](primitiveValidator, value))
}
}
trait LowPriorityValidatorForPredicate {
implicit def genericValidatorForPredicate[V, P: ClassTag](implicit
refinedValidator: Validate[V, P]
): ValidatorForPredicate[V, P] =
new ValidatorForPredicate[V, P] {
override val validator: Validator.Custom[V] = Validator.Custom(
{ v =>
if (refinedValidator.isValid(v)) ValidationResult.Valid
else ValidationResult.Invalid(implicitly[ClassTag[P]].runtimeClass.toString)
}
) // for the moment there is no way to get a human description of a predicate/validator without having a concrete value to run it
override def validationErrors(value: V, refinedErrorMessage: String): List[ValidationError[_]] =
List(ValidationError[V](validator, value, Nil, Some(refinedErrorMessage)))
}
} Can you give me some guidance how I can rewrite the codes for Iron? |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 2 replies
-
Hi 👋 I never used Tapir but I suggest you to check the ZIO JSON Module. The process should be similar. Note: I'm not suggesting you to switch to ZIO JSON but to look the module's code because Tapir support probably can be done in a similar way. I think it should look like this: //> using scala "3.2.2"
//> using lib "io.github.iltotore::iron:2.1.0"
//> using lib "com.softwaremill.sttp.tapir::tapir-core:1.3.0"
import io.github.iltotore.iron.*
import io.github.iltotore.iron.constraint.all.*
import sttp.tapir.{Schema, Validator, ValidationResult}
inline def ironValidator[A, C](inline constraint: Constraint[A, C]): Validator[A] =
Validator.custom(
value =>
if constraint.test(value) then ValidationResult.Valid
else ValidationResult.Invalid(List(s"Iron constraint failed for $value: ${constraint.message}")),
Some(constraint.message)
)
inline given [A, C](using inline baseSchema: Schema[A], inline constraint: Constraint[A, C]): Schema[A :| C] =
baseSchema
.validate(ironValidator(constraint))
.map(value => Some[A :| C](value.assume[C]))(x => x) The code compile but I'm not sure how I should test such |
Beta Was this translation helpful? Give feedback.
-
Thank you, @Iltotore for your answer. I shall try it. Unfortunately, I can't switch Tapir to other dependency as it's actually not for my app. |
Beta Was this translation helpful? Give feedback.
Hi 👋
I never used Tapir but I suggest you to check the ZIO JSON Module. The process should be similar.
Note: I'm not suggesting you to switch to ZIO JSON but to look the module's code because Tapir support probably can be done in a similar way.
I think it should look like this: