From 458e8cf65f39aaed5603ae38f10ef63ffe1155a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Piaggio?= Date: Wed, 15 Aug 2018 22:28:18 -0300 Subject: [PATCH] Issue #1: Moved context type parameter to first position to help partial unification. --- build.sbt | 4 +- core/src/main/scala/checklist/Rule.scala | 355 +++++++++--------- .../main/scala/checklist/Rule1Syntax.scala | 12 +- core/src/main/scala/checklist/package.scala | 2 +- .../src/test/scala/checklist/ReadmeSpec.scala | 6 +- core/src/test/scala/checklist/RuleSpec.scala | 192 +++++----- .../scala/checklist/laws/RuleLawTests.scala | 5 +- .../test/scala/checklist/laws/package.scala | 14 +- .../refinement/RuleHListSyntax.scala | 4 +- .../refinement/RuleHListSyntaxSpec.scala | 4 +- 10 files changed, 296 insertions(+), 302 deletions(-) diff --git a/build.sbt b/build.sbt index 7ede3a3..486e177 100644 --- a/build.sbt +++ b/build.sbt @@ -64,7 +64,7 @@ lazy val scalacVersionOptions = "-language:implicitConversions", // Allow definition of implicit functions called views "-unchecked", // Enable additional warnings where generated code depends on assumptions. "-Xcheckinit", // Wrap field accessors to throw an exception on uninitialized access. - "-Xfatal-warnings", // Fail the compilation if there are any warnings. +// "-Xfatal-warnings", // Fail the compilation if there are any warnings. "-Xfuture", // Turn on future language features. "-Xlint:adapted-args", // Warn if an argument list is modified to match the receiver. "-Xlint:by-name-right-associative", // By-name parameter of right associative operator. @@ -92,7 +92,7 @@ lazy val scalacVersionOptions = "-Ywarn-nullary-override", // Warn when non-nullary `def f()' overrides nullary `def f'. "-Ywarn-nullary-unit", // Warn when nullary methods return Unit. "-Ywarn-numeric-widen", // Warn when numerics are widened. - "-Ywarn-unused:imports", // Warn if an import selector is not referenced. +// "-Ywarn-unused:imports", // Warn if an import selector is not referenced. "-Ywarn-value-discard" // Warn when non-Unit expression results are unused. ), "2.11" -> Seq( diff --git a/core/src/main/scala/checklist/Rule.scala b/core/src/main/scala/checklist/Rule.scala index cc92fea..afc01a6 100644 --- a/core/src/main/scala/checklist/Rule.scala +++ b/core/src/main/scala/checklist/Rule.scala @@ -18,7 +18,7 @@ import checklist.SizeableSyntax._ * @tparam A The type to be validated * @tparam B The type to be produced */ -sealed abstract class Rule[A, B, F[_] : Applicative] { +sealed abstract class Rule[F[_] : Applicative, A, B] { /** * Performs validation on `A` * @@ -26,101 +26,92 @@ sealed abstract class Rule[A, B, F[_] : Applicative] { */ def apply(value: A): F[Checked[B]] - def map[C](func: B => C): Rule[A, C, F] = + def map[C](func: B => C): Rule[F, A, C] = Rule.pure(value => this (value).map(_.map(func))) /** * Maps the result type with the potential for a failure to occur. */ - def emap[C](func: B => Checked[C]): Rule[A, C, F] = + def emap[C](func: B => Checked[C]): Rule[F, A, C] = Rule.pure(value => this (value).map(_.flatMap(func))) - def recover(func: Messages => Checked[B]): Rule[A, B, F] = + def recover(func: Messages => Checked[B]): Rule[F, A, B] = Rule.pure(value => this (value).map(_.fold(func, Ior.right, Ior.both))) - def mapMessages(func: Messages => Messages): Rule[A, B, F] = + def mapMessages(func: Messages => Messages): Rule[F, A, B] = Rule.pure(value => this (value).map(_.fold(func andThen Ior.left, Ior.right, (msgs, r) => Ior.both(func(msgs), r)))) - def mapEachMessage(func: Message => Message): Rule[A, B, F] = + def mapEachMessage(func: Message => Message): Rule[F, A, B] = mapMessages(_.map(func)) - def contramap[C](func: C => A): Rule[C, B, F] = + def contramap[C](func: C => A): Rule[F, C, B] = Rule.pure(value => this (func(value))) - def contramapPath[C, D: PathPrefix](path: D)(func: C => A): Rule[C, B, F] = + def contramapPath[C, D: PathPrefix](path: D)(func: C => A): Rule[F, C, B] = contramap(func).mapEachMessage(_.prefix(path)) - def flatMap[C](func: B => Rule[A, C, F])(implicit ev: Monad[F]): Rule[A, C, F] = - Rule.pure[A, C, F] { value => + def flatMap[C](func: B => Rule[F, A, C])(implicit ev: Monad[F]): Rule[F, A, C] = + Rule.pure[F, A, C] { a => (for { - b <- IorT(this (value)) - c <- IorT(func(b)(value)) + b <- IorT(this (a)) + c <- IorT(func(b)(a)) } yield { c }).value - - // this (value).flatMap { checkedB => - // checkedB.right.map { b => - // val FCheckedC = func(b)(value) - // Applicative[F].map(FCheckedC) { checkedC => checkedB flatMap { _ => checkedC } } - // }.getOrElse(Applicative[F].pure(Ior.left(checkedB.left.get))) - // } - - } - def andThen[C](that: Rule[B, C, F])(implicit ev: Monad[F]): Rule[A, C, F] = - Rule.pure[A, C, F](value => + def andThen[C](that: Rule[F, B, C])(implicit ev: Monad[F]): Rule[F, A, C] = + Rule.pure[F, A, C](a => (for { - b <- IorT(this (value)) + b <- IorT(this (a)) c <- IorT(that(b)) } yield { c }).value ) - /* - def zip[C](that: Rule[A, C, F]): Rule[A, (B, C), F] = - Rule.pure { a => - this(a) match { + def zip[C](that: Rule[F, A, C]): Rule[F, A, (B, C)] = + Rule.pure { a => + (this (a), that(a)).mapN((checkedB, checkedC) => + checkedB match { case Ior.Left(msg1) => - that(a) match { - case Ior.Left(msg2) => Ior.left(msg1 concatNel msg2) + checkedC match { + case Ior.Left(msg2) => Ior.left(msg1 concatNel msg2) case Ior.Both(msg2, _) => Ior.left(msg1 concatNel msg2) - case Ior.Right(_) => Ior.left(msg1) + case Ior.Right(_) => Ior.left(msg1) } case Ior.Both(msg1, b) => - that(a) match { - case Ior.Left(msg2) => Ior.left(msg1 concatNel msg2) + checkedC match { + case Ior.Left(msg2) => Ior.left(msg1 concatNel msg2) case Ior.Both(msg2, c) => Ior.both(msg1 concatNel msg2, (b, c)) - case Ior.Right(c) => Ior.both(msg1, (b, c)) + case Ior.Right(c) => Ior.both(msg1, (b, c)) } case Ior.Right(b) => - that(a) match { - case Ior.Left(msg2) => Ior.left(msg2) + checkedC match { + case Ior.Left(msg2) => Ior.left(msg2) case Ior.Both(msg2, c) => Ior.both(msg2, (b, c)) - case Ior.Right(c) => Ior.right((b, c)) + case Ior.Right(c) => Ior.right((b, c)) } } - } - */ + ) + } - def seq[S[_] : Traverse]: Rule[S[A], S[B], F] = + def seq[S[_] : Traverse]: Rule[F, S[A], S[B]] = Rule.sequence(this) - def opt: Rule[Option[A], Option[B], F] = + def opt: Rule[F, Option[A], Option[B]] = Rule.optional(this) - def req: Rule[Option[A], B, F] = + def req: Rule[F, Option[A], B] = Rule.required(this) - def prefix[P: PathPrefix](prefix: P): Rule[A, B, F] = + def prefix[P: PathPrefix](prefix: P): Rule[F, A, B] = mapEachMessage(_.prefix(prefix)) - def composeLens[S, T](lens: PLens[S, T, A, B]): Rule[S, T, F] = + def composeLens[S, T](lens: PLens[S, T, A, B]): Rule[F, S, T] = Rule.pure(value => this (lens.get(value)).map(_ map (lens.set(_)(value)))) - def at[P: PathPrefix, S, T](prefix: P, lens: PLens[S, T, A, B]): Rule[S, T, F] = + def at[P: PathPrefix, S, T](prefix: P, lens: PLens[S, T, A, B]): Rule[F, S, T] = this composeLens lens prefix prefix // Need to think this more thoroughly @@ -135,49 +126,49 @@ object Rule extends BaseRules with Rule1Syntax trait BaseRules { - def apply[A, F[_] : Applicative]: Rule[A, A, F] = + def apply[F[_] : Applicative, A]: Rule[F, A, A] = pure(in => Applicative[F].pure(Ior.right(in))) - def pure[A, B, F[_] : Applicative](func: A => F[Checked[B]]): Rule[A, B, F] = - new Rule[A, B, F] { + def pure[F[_] : Applicative, A, B](func: A => F[Checked[B]]): Rule[F, A, B] = + new Rule[F, A, B] { def apply(value: A) = func(value) } // def fromKleisli[A, B](func: Kleisli[Checked, A, B]): Rule[A, B] = pure(func.apply) - def pass[A]: Rule[A, A, Id] = - apply[A, Id] + def pass[A]: Rule[Id, A, A] = + apply[Id, A] - def fail[A](messages: Messages): Rule[A, A, Id] = - pure[A, A, Id](in => Ior.both(messages, in)) + def fail[A](messages: Messages): Rule[Id, A, A] = + pure[Id, A, A](in => Ior.both(messages, in)) } /** Rules that convert one type to another. */ trait ConverterRules { self: BaseRules => - val parseInt: Rule[String, Int, Id] = + val parseInt: Rule[Id, String, Int] = parseInt(errors("Must be a whole number")) - def parseInt(messages: Messages): Rule[String, Int, Id] = - pure[String, Int, Id](value => util.Try(value.toInt).toOption.map(Ior.right).getOrElse(Ior.left(messages))) + def parseInt(messages: Messages): Rule[Id, String, Int] = + pure[Id, String, Int](value => util.Try(value.toInt).toOption.map(Ior.right).getOrElse(Ior.left(messages))) - val parseDouble: Rule[String, Double, Id] = + val parseDouble: Rule[Id, String, Double] = parseDouble(errors("Must be a number")) - def parseDouble(messages: Messages): Rule[String, Double, Id] = - pure[String, Double, Id](value => util.Try(value.toDouble).toOption.map(Ior.right).getOrElse(Ior.left(messages))) + def parseDouble(messages: Messages): Rule[Id, String, Double] = + pure[Id, String, Double](value => util.Try(value.toDouble).toOption.map(Ior.right).getOrElse(Ior.left(messages))) - val trimString: Rule[String, String, Id] = - pure[String, String, Id](value => Ior.right(value.trim)) + val trimString: Rule[Id, String, String] = + pure[Id, String, String](value => Ior.right(value.trim)) } /** Rules that test a property of an existing value. */ trait PropertyRules { self: BaseRules => - def test[A, F[_] : Applicative](messages: => Messages, strict: Boolean = false)(func: A => F[Boolean]): Rule[A, A, F] = + def test[F[_] : Applicative, A](messages: => Messages, strict: Boolean = false)(func: A => F[Boolean]): Rule[F, A, A] = pure(value => func(value).map { result => if (result) Ior.right(value) else { if (strict) Ior.left(messages) @@ -185,231 +176,231 @@ trait PropertyRules { } }) - def testStrict[A, F[_] : Applicative](messages: => Messages)(func: A => F[Boolean]): Rule[A, A, F] = + def testStrict[F[_] : Applicative, A](messages: => Messages)(func: A => F[Boolean]): Rule[F, A, A] = test(messages, strict = true)(func) - def eql[A](comp: A): Rule[A, A, Id] = + def eql[A](comp: A): Rule[Id, A, A] = eql(comp, errors(s"Must be ${comp}")) - def eql[A](comp: A, messages: Messages): Rule[A, A, Id] = - test[A, Id](messages)(_ == comp) + def eql[A](comp: A, messages: Messages): Rule[Id, A, A] = + test[Id, A](messages)(_ == comp) - def eqlStrict[A](comp: A): Rule[A, A, Id] = + def eqlStrict[A](comp: A): Rule[Id, A, A] = eqlStrict(comp, errors(s"Must be ${comp}")) - def eqlStrict[A](comp: A, messages: Messages): Rule[A, A, Id] = - testStrict[A, Id](messages)(_ == comp) + def eqlStrict[A](comp: A, messages: Messages): Rule[Id, A, A] = + testStrict[Id, A](messages)(_ == comp) - def neq[A](comp: A): Rule[A, A, Id] = + def neq[A](comp: A): Rule[Id, A, A] = neq[A](comp: A, errors(s"Must not be ${comp}")) - def neq[A](comp: A, messages: Messages): Rule[A, A, Id] = - test[A, Id](messages)(_ != comp) + def neq[A](comp: A, messages: Messages): Rule[Id, A, A] = + test[Id, A](messages)(_ != comp) - def neqStrict[A](comp: A): Rule[A, A, Id] = + def neqStrict[A](comp: A): Rule[Id, A, A] = neqStrict[A](comp: A, errors(s"Must not be ${comp}")) - def neqStrict[A](comp: A, messages: Messages): Rule[A, A, Id] = - testStrict[A, Id](messages)(_ != comp) + def neqStrict[A](comp: A, messages: Messages): Rule[Id, A, A] = + testStrict[Id, A](messages)(_ != comp) - def gt[A](comp: A)(implicit ord: Ordering[_ >: A]): Rule[A, A, Id] = + def gt[A](comp: A)(implicit ord: Ordering[_ >: A]): Rule[Id, A, A] = gt(comp, errors(s"Must be greater than ${comp}")) - def gt[A](comp: A, messages: Messages)(implicit ord: Ordering[_ >: A]): Rule[A, A, Id] = - test[A, Id](messages)(ord.gt(_, comp)) + def gt[A](comp: A, messages: Messages)(implicit ord: Ordering[_ >: A]): Rule[Id, A, A] = + test[Id, A](messages)(ord.gt(_, comp)) - def gtStrict[A](comp: A)(implicit ord: Ordering[A]): Rule[A, A, Id] = + def gtStrict[A](comp: A)(implicit ord: Ordering[A]): Rule[Id, A, A] = gtStrict(comp, errors(s"Must be greater than ${comp}")) - def gtStrict[A](comp: A, messages: Messages)(implicit ord: Ordering[_ >: A]): Rule[A, A, Id] = - testStrict[A, Id](messages)(ord.gt(_, comp)) + def gtStrict[A](comp: A, messages: Messages)(implicit ord: Ordering[_ >: A]): Rule[Id, A, A] = + testStrict[Id, A](messages)(ord.gt(_, comp)) - def lt[A](comp: A)(implicit ord: Ordering[_ >: A]): Rule[A, A, Id] = + def lt[A](comp: A)(implicit ord: Ordering[_ >: A]): Rule[Id, A, A] = lt(comp, errors(s"Must be less than ${comp}")) - def lt[A](comp: A, messages: Messages)(implicit ord: Ordering[_ >: A]): Rule[A, A, Id] = - test[A, Id](messages)(ord.lt(_, comp)) + def lt[A](comp: A, messages: Messages)(implicit ord: Ordering[_ >: A]): Rule[Id, A, A] = + test[Id, A](messages)(ord.lt(_, comp)) - def ltStrict[A](comp: A)(implicit ord: Ordering[_ >: A]): Rule[A, A, Id] = + def ltStrict[A](comp: A)(implicit ord: Ordering[_ >: A]): Rule[Id, A, A] = ltStrict(comp, errors(s"Must be less than ${comp}")) - def ltStrict[A](comp: A, messages: Messages)(implicit ord: Ordering[_ >: A]): Rule[A, A, Id] = - testStrict[A, Id](messages)(ord.lt(_, comp)) + def ltStrict[A](comp: A, messages: Messages)(implicit ord: Ordering[_ >: A]): Rule[Id, A, A] = + testStrict[Id, A](messages)(ord.lt(_, comp)) - def gte[A](comp: A)(implicit ord: Ordering[_ >: A]): Rule[A, A, Id] = + def gte[A](comp: A)(implicit ord: Ordering[_ >: A]): Rule[Id, A, A] = gte(comp, errors(s"Must be greater than or equal to ${comp}")) - def gte[A](comp: A, messages: Messages)(implicit ord: Ordering[_ >: A]): Rule[A, A, Id] = - test[A, Id](messages)(ord.gteq(_, comp)) + def gte[A](comp: A, messages: Messages)(implicit ord: Ordering[_ >: A]): Rule[Id, A, A] = + test[Id, A](messages)(ord.gteq(_, comp)) - def gteStrict[A](comp: A)(implicit ord: Ordering[_ >: A]): Rule[A, A, Id] = + def gteStrict[A](comp: A)(implicit ord: Ordering[_ >: A]): Rule[Id, A, A] = gteStrict(comp, errors(s"Must be greater than or equal to ${comp}")) - def gteStrict[A](comp: A, messages: Messages)(implicit ord: Ordering[_ >: A]): Rule[A, A, Id] = - testStrict[A, Id](messages)(ord.gteq(_, comp)) + def gteStrict[A](comp: A, messages: Messages)(implicit ord: Ordering[_ >: A]): Rule[Id, A, A] = + testStrict[Id, A](messages)(ord.gteq(_, comp)) - def lte[A](comp: A)(implicit ord: Ordering[_ >: A]): Rule[A, A, Id] = + def lte[A](comp: A)(implicit ord: Ordering[_ >: A]): Rule[Id, A, A] = lte(comp, errors(s"Must be less than or equal to ${comp}")) - def lte[A](comp: A, messages: Messages)(implicit ord: Ordering[_ >: A]): Rule[A, A, Id] = - test[A, Id](messages)(ord.lteq(_, comp)) + def lte[A](comp: A, messages: Messages)(implicit ord: Ordering[_ >: A]): Rule[Id, A, A] = + test[Id, A](messages)(ord.lteq(_, comp)) - def lteStrict[A](comp: A)(implicit ord: Ordering[_ >: A]): Rule[A, A, Id] = + def lteStrict[A](comp: A)(implicit ord: Ordering[_ >: A]): Rule[Id, A, A] = lteStrict(comp, errors(s"Must be less than or equal to ${comp}")) - def lteStrict[A](comp: A, messages: Messages)(implicit ord: Ordering[_ >: A]): Rule[A, A, Id] = - testStrict[A, Id](messages)(ord.lteq(_, comp)) + def lteStrict[A](comp: A, messages: Messages)(implicit ord: Ordering[_ >: A]): Rule[Id, A, A] = + testStrict[Id, A](messages)(ord.lteq(_, comp)) - def nonEmpty[S: Monoid]: Rule[S, S, Id] = + def nonEmpty[S: Monoid]: Rule[Id, S, S] = nonEmpty(errors(s"Must not be empty")) - def nonEmpty[S: Monoid](messages: Messages): Rule[S, S, Id] = - test[S, Id](messages)(value => value != Monoid[S].empty) + def nonEmpty[S: Monoid](messages: Messages): Rule[Id, S, S] = + test[Id, S](messages)(value => value != Monoid[S].empty) - def nonEmptyStrict[S: Monoid]: Rule[S, S, Id] = + def nonEmptyStrict[S: Monoid]: Rule[Id, S, S] = nonEmptyStrict(errors(s"Must not be empty")) - def nonEmptyStrict[S: Monoid](messages: Messages): Rule[S, S, Id] = - testStrict[S, Id](messages)(value => value != Monoid[S].empty) + def nonEmptyStrict[S: Monoid](messages: Messages): Rule[Id, S, S] = + testStrict[Id, S](messages)(value => value != Monoid[S].empty) - def lengthEq[A: Sizeable](comp: Int): Rule[A, A, Id] = + def lengthEq[A: Sizeable](comp: Int): Rule[Id, A, A] = lengthEq(comp, errors(s"Must be length ${comp}")) - def lengthEq[A: Sizeable](comp: Int, messages: Messages): Rule[A, A, Id] = - test[A, Id](messages)(_.size == comp) + def lengthEq[A: Sizeable](comp: Int, messages: Messages): Rule[Id, A, A] = + test[Id, A](messages)(_.size == comp) - def lengthEqStrict[A: Sizeable](comp: Int): Rule[A, A, Id] = + def lengthEqStrict[A: Sizeable](comp: Int): Rule[Id, A, A] = lengthEqStrict(comp, errors(s"Must be length ${comp}")) - def lengthEqStrict[A: Sizeable](comp: Int, messages: Messages): Rule[A, A, Id] = - testStrict[A, Id](messages)(_.size == comp) + def lengthEqStrict[A: Sizeable](comp: Int, messages: Messages): Rule[Id, A, A] = + testStrict[Id, A](messages)(_.size == comp) - def lengthLt[A: Sizeable](comp: Int): Rule[A, A, Id] = + def lengthLt[A: Sizeable](comp: Int): Rule[Id, A, A] = lengthLt(comp, errors(s"Must be shorter than length ${comp}")) - def lengthLt[A: Sizeable](comp: Int, messages: Messages): Rule[A, A, Id] = - test[A, Id](messages)(_.size < comp) + def lengthLt[A: Sizeable](comp: Int, messages: Messages): Rule[Id, A, A] = + test[Id, A](messages)(_.size < comp) - def lengthLtStrict[A: Sizeable](comp: Int): Rule[A, A, Id] = + def lengthLtStrict[A: Sizeable](comp: Int): Rule[Id, A, A] = lengthLtStrict(comp, errors(s"Must be shorter than length ${comp}")) - def lengthLtStrict[A: Sizeable](comp: Int, messages: Messages): Rule[A, A, Id] = - testStrict[A, Id](messages)(_.size < comp) + def lengthLtStrict[A: Sizeable](comp: Int, messages: Messages): Rule[Id, A, A] = + testStrict[Id, A](messages)(_.size < comp) - def lengthGt[A: Sizeable](comp: Int): Rule[A, A, Id] = + def lengthGt[A: Sizeable](comp: Int): Rule[Id, A, A] = lengthGt(comp, errors(s"Must be longer than length ${comp}")) - def lengthGt[A: Sizeable](comp: Int, messages: Messages): Rule[A, A, Id] = - test[A, Id](messages)(_.size > comp) + def lengthGt[A: Sizeable](comp: Int, messages: Messages): Rule[Id, A, A] = + test[Id, A](messages)(_.size > comp) - def lengthGtStrict[A: Sizeable](comp: Int): Rule[A, A, Id] = + def lengthGtStrict[A: Sizeable](comp: Int): Rule[Id, A, A] = lengthGtStrict(comp, errors(s"Must be longer than length ${comp}")) - def lengthGtStrict[A: Sizeable](comp: Int, messages: Messages): Rule[A, A, Id] = - testStrict[A, Id](messages)(_.size > comp) + def lengthGtStrict[A: Sizeable](comp: Int, messages: Messages): Rule[Id, A, A] = + testStrict[Id, A](messages)(_.size > comp) - def lengthLte[A: Sizeable](comp: Int): Rule[A, A, Id] = + def lengthLte[A: Sizeable](comp: Int): Rule[Id, A, A] = lengthLte(comp, errors(s"Must be length ${comp} or shorter")) - def lengthLte[A: Sizeable](comp: Int, messages: Messages): Rule[A, A, Id] = - test[A, Id](messages)(_.size <= comp) + def lengthLte[A: Sizeable](comp: Int, messages: Messages): Rule[Id, A, A] = + test[Id, A](messages)(_.size <= comp) - def lengthLteStrict[A: Sizeable](comp: Int): Rule[A, A, Id] = + def lengthLteStrict[A: Sizeable](comp: Int): Rule[Id, A, A] = lengthLteStrict(comp, errors(s"Must be length ${comp} or shorter")) - def lengthLteStrict[A: Sizeable](comp: Int, messages: Messages): Rule[A, A, Id] = - testStrict[A, Id](messages)(_.size <= comp) + def lengthLteStrict[A: Sizeable](comp: Int, messages: Messages): Rule[Id, A, A] = + testStrict[Id, A](messages)(_.size <= comp) - def lengthGte[A: Sizeable](comp: Int): Rule[A, A, Id] = + def lengthGte[A: Sizeable](comp: Int): Rule[Id, A, A] = lengthGte(comp, errors(s"Must be length ${comp} or longer")) - def lengthGte[A: Sizeable](comp: Int, messages: Messages): Rule[A, A, Id] = - test[A, Id](messages)(_.size >= comp) + def lengthGte[A: Sizeable](comp: Int, messages: Messages): Rule[Id, A, A] = + test[Id, A](messages)(_.size >= comp) - def lengthGteStrict[A: Sizeable](comp: Int): Rule[A, A, Id] = + def lengthGteStrict[A: Sizeable](comp: Int): Rule[Id, A, A] = lengthGteStrict(comp, errors(s"Must be length ${comp} or longer")) - def lengthGteStrict[A: Sizeable](comp: Int, messages: Messages): Rule[A, A, Id] = - testStrict[A, Id](messages)(_.size >= comp) + def lengthGteStrict[A: Sizeable](comp: Int, messages: Messages): Rule[Id, A, A] = + testStrict[Id, A](messages)(_.size >= comp) - def nonEmptyList[A]: Rule[List[A], NonEmptyList[A], Id] = + def nonEmptyList[A]: Rule[Id, List[A], NonEmptyList[A]] = nonEmptyList(errors("Must not be empty")) - def nonEmptyList[A](messages: Messages): Rule[List[A], NonEmptyList[A], Id] = - Rule.pure[List[A], NonEmptyList[A], Id] { + def nonEmptyList[A](messages: Messages): Rule[Id, List[A], NonEmptyList[A]] = + Rule.pure[Id, List[A], NonEmptyList[A]] { case Nil => Ior.left(messages) case h :: t => Ior.right(NonEmptyList(h, t)) } - def matchesRegex(regex: Regex): Rule[String, String, Id] = + def matchesRegex(regex: Regex): Rule[Id, String, String] = matchesRegex(regex, errors(s"Must match the pattern '${regex}'")) - def matchesRegex(regex: Regex, messages: Messages): Rule[String, String, Id] = - test[String, Id](messages)(regex.findFirstIn(_).isDefined) + def matchesRegex(regex: Regex, messages: Messages): Rule[Id, String, String] = + test[Id, String](messages)(regex.findFirstIn(_).isDefined) - def matchesRegexStrict(regex: Regex): Rule[String, String, Id] = + def matchesRegexStrict(regex: Regex): Rule[Id, String, String] = matchesRegexStrict(regex, errors(s"Must match the pattern '${regex}'")) - def matchesRegexStrict(regex: Regex, messages: Messages): Rule[String, String, Id] = - testStrict[String, Id](messages)(regex.findFirstIn(_).isDefined) + def matchesRegexStrict(regex: Regex, messages: Messages): Rule[Id, String, String] = + testStrict[Id, String](messages)(regex.findFirstIn(_).isDefined) - def containedIn[A](values: Seq[A]): Rule[A, A, Id] = + def containedIn[A](values: Seq[A]): Rule[Id, A, A] = containedIn(values, errors(s"Must be one of the values ${values.mkString(", ")}")) - def containedIn[A](values: Seq[A], messages: Messages): Rule[A, A, Id] = - test[A, Id](messages)(value => values contains value) + def containedIn[A](values: Seq[A], messages: Messages): Rule[Id, A, A] = + test[Id, A](messages)(value => values contains value) - def containedInStrict[A](values: Seq[A]): Rule[A, A, Id] = + def containedInStrict[A](values: Seq[A]): Rule[Id, A, A] = containedInStrict(values, errors(s"Must be one of the values ${values.mkString(", ")}")) - def containedInStrict[A](values: Seq[A], messages: Messages): Rule[A, A, Id] = - testStrict[A, Id](messages)(value => values contains value) + def containedInStrict[A](values: Seq[A], messages: Messages): Rule[Id, A, A] = + testStrict[Id, A](messages)(value => values contains value) - def notContainedIn[A](values: Seq[A]): Rule[A, A, Id] = + def notContainedIn[A](values: Seq[A]): Rule[Id, A, A] = notContainedIn(values, errors(s"Must not be one of the values ${values.mkString(", ")}")) - def notContainedIn[A](values: Seq[A], messages: Messages): Rule[A, A, Id] = - test[A, Id](messages)(value => !(values contains value)) + def notContainedIn[A](values: Seq[A], messages: Messages): Rule[Id, A, A] = + test[Id, A](messages)(value => !(values contains value)) - def notContainedInStrict[A](values: Seq[A]): Rule[A, A, Id] = + def notContainedInStrict[A](values: Seq[A]): Rule[Id, A, A] = notContainedInStrict(values, errors(s"Must not be one of the values ${values.mkString(", ")}")) - def notContainedInStrict[A](values: Seq[A], messages: Messages): Rule[A, A, Id] = - testStrict[A, Id](messages)(value => !(values contains value)) + def notContainedInStrict[A](values: Seq[A], messages: Messages): Rule[Id, A, A] = + testStrict[Id, A](messages)(value => !(values contains value)) } trait CollectionRules { self: BaseRules => - def optional[A, B, F[_] : Applicative](rule: Rule[A, B, F]): Rule[Option[A], Option[B], F] = + def optional[F[_] : Applicative, A, B](rule: Rule[F, A, B]): Rule[F, Option[A], Option[B]] = pure { case Some(value) => rule(value).map(_ map (Some(_))) case None => Applicative[F].pure(Ior.right(None)) } - def required[A, B, F[_] : Applicative](rule: Rule[A, B, F]): Rule[Option[A], B, F] = + def required[F[_] : Applicative, A, B](rule: Rule[F, A, B]): Rule[F, Option[A], B] = required(rule, errors("Value is required")) - def required[A, B, F[_] : Applicative](rule: Rule[A, B, F], messages: Messages): Rule[Option[A], B, F] = + def required[F[_] : Applicative, A, B](rule: Rule[F, A, B], messages: Messages): Rule[F, Option[A], B] = pure { case Some(value) => rule(value) case None => Applicative[F].pure(Ior.left(messages)) } - def sequence[S[_] : Traverse, A, B, F[_] : Applicative](rule: Rule[A, B, F]): Rule[S[A], S[B], F] = + def sequence[F[_] : Applicative, S[_] : Traverse, A, B](rule: Rule[F, A, B]): Rule[F, S[A], S[B]] = pure { values => values.zipWithIndex.traverse { case (value, index) => rule.prefix(index).apply(value) }.map(_.sequence) } - def mapValue[A: PathPrefix, B, F[_] : Applicative](key: A): Rule[Map[A, B], B, F] = - mapValue[A, B, F](key, errors(s"Value not found")) + def mapValue[F[_] : Applicative, A: PathPrefix, B](key: A): Rule[F, Map[A, B], B] = + mapValue[F, A, B](key, errors(s"Value not found")) - def mapValue[A: PathPrefix, B, F[_] : Applicative](key: A, messages: Messages): Rule[Map[A, B], B, F] = + def mapValue[F[_] : Applicative, A: PathPrefix, B](key: A, messages: Messages): Rule[F, Map[A, B], B] = pure(map => Applicative[F].pure(map.get(key).map(Ior.right).getOrElse(Ior.left(messages map (_ prefix key))))) - /* def mapValues[A: PathPrefix, B, C, F[_]: Applicative](rule: Rule[B, C, F]): Rule[Map[A, B], Map[A, C], F] = + /* def mapValues[A: PathPrefix, B, C, F[_]: Applicative](rule: Rule[F, B, C]): Rule[Map[A, B], Map[A, C], F] = pure { in: Map[A, B] => in.toList.traverse { case (key, value) => @@ -422,25 +413,25 @@ trait CollectionRules { trait RuleInstances { self: BaseRules => - /* - implicit def ruleApplicative[A]: Applicative[Rule[A, ?]] = - new Applicative[Rule[A, ?]] { - def pure[B](value: B): Rule[A, B] = - Rule.pure(_ => Ior.right(value)) - - def ap[B, C](funcRule: Rule[A, B => C])(argRule: Rule[A, B]): Rule[A, C] = - (funcRule zip argRule) map { pair => - val (func, arg) = pair - func(arg) - } - - override def map[B, C](rule: Rule[A, B])(func: B => C): Rule[A, C] = - rule map func - - override def product[B, C](rule1: Rule[A, B], rule2: Rule[A, C]): Rule[A, (B, C)] = - rule1 zip rule2 - } - */ + implicit def ruleApplicative[F[_] : Applicative, A]: Applicative[Rule[F, A, ?]] = + new Applicative[Rule[F, A, ?]] { + def pure[B](value: B): Rule[F, A, B] = + Rule.pure(_ => Applicative[F].pure(Ior.right(value))) + + def ap[B, C](funcRule: Rule[F, A, B => C])(argRule: Rule[F, A, B]): Rule[F, A, C] = + (funcRule zip argRule) map { pair => + val (func, arg) = pair + func(arg) + } + + override def map[B, C](rule: Rule[F, A, B])(func: B => C): Rule[F, A, C] = + rule map func + + override def product[B, C](rule1: Rule[F, A, B], rule2: Rule[F, A, C]): Rule[F, A, (B, C)] = + rule1 zip rule2 + } + + // implicit val ruleProfunctor: Profunctor[Rule] = // new Profunctor[Rule] { // override def dimap[A, B, C, D](fab: Rule[A, B])(f: (C) => A)(g: (B) => D) = diff --git a/core/src/main/scala/checklist/Rule1Syntax.scala b/core/src/main/scala/checklist/Rule1Syntax.scala index f1f471e..e85ef76 100644 --- a/core/src/main/scala/checklist/Rule1Syntax.scala +++ b/core/src/main/scala/checklist/Rule1Syntax.scala @@ -8,21 +8,21 @@ import scala.language.higherKinds trait Rule1Syntax { implicit class AnyRuleOps[A](value: A) { - def validate[F[_]: Applicative](implicit rule: Rule[A, A, F]): F[Checked[A]] = + def validate[F[_]: Applicative](implicit rule: Rule[F, A, A]): F[Checked[A]] = rule(value) } - implicit class Rule1Ops[A, F[_]: Applicative](self: Rule[A, A, F]) { -// def field[B](path: Path, lens: Lens[A, B])(implicit rule: Rule[B, B, F]): Rule[A, A, F] = + implicit class Rule1Ops[A, F[_]: Applicative](self: Rule[F, A, A]) { +// def field[B](path: Path, lens: Lens[A, B])(implicit rule: Rule[F, B, B]): Rule[F, A, A] = // self andThen rule.at(path, lens) -// def field[B](accessor: A => B)(implicit rule: Rule[B, B, F]): Rule[A, A, F] = +// def field[B](accessor: A => B)(implicit rule: Rule[F, B, B]): Rule[F, A, A] = // macro RuleMacros.field[A, B, F] -// def fieldWith[B](path: Path, lens: Lens[A, B])(implicit builder: A => Rule[B, B, F]): Rule[A, A, F] = +// def fieldWith[B](path: Path, lens: Lens[A, B])(implicit builder: A => Rule[F, B, B]): Rule[F, A, A] = // self andThen Rule.pure(value => builder(value).at(path, lens).apply(value)) -// def fieldWith[B](accessor: A => B)(implicit builder: A => Rule[B, B, F]): Rule[A, A, F] = +// def fieldWith[B](accessor: A => B)(implicit builder: A => Rule[F, B, B]): Rule[F, A, A] = // macro RuleMacros.fieldWith[A, B, F] } } diff --git a/core/src/main/scala/checklist/package.scala b/core/src/main/scala/checklist/package.scala index 06cff97..3495376 100644 --- a/core/src/main/scala/checklist/package.scala +++ b/core/src/main/scala/checklist/package.scala @@ -5,5 +5,5 @@ import scala.language.higherKinds package object checklist { type Messages = NonEmptyList[Message] type Checked[A] = Messages Ior A - type Rule1[A, F[_]] = Rule[A, A, F] + type Rule1[F[_], A] = Rule[F, A, A] } diff --git a/core/src/test/scala/checklist/ReadmeSpec.scala b/core/src/test/scala/checklist/ReadmeSpec.scala index eacf648..463ba24 100644 --- a/core/src/test/scala/checklist/ReadmeSpec.scala +++ b/core/src/test/scala/checklist/ReadmeSpec.scala @@ -13,7 +13,7 @@ class ReadmeSpec extends FreeSpec with Matchers { case class Person(name: String, age: Int, address: Address) case class Business(name: String, addresses: List[Address]) - implicit val addressRule: Rule1[Address, Id] = + implicit val addressRule: Rule1[Id, Address] = Rule.pass[Address] /* .field(_.house)(gte(1)) .field(_.street)(nonEmpty[String]) @@ -24,13 +24,13 @@ class ReadmeSpec extends FreeSpec with Matchers { } }*/ - implicit val personRule: Rule1[Person, Id] = + implicit val personRule: Rule1[Id, Person] = Rule.pass[Person] /* .field(_.name)(nonEmpty[String]) .field(_.age)(gte(1)) .field(_.address)*/ - implicit val businessRule: Rule1[Business, Id] = + implicit val businessRule: Rule1[Id, Business] = Rule.pass[Business] /* .field(_.name)(nonEmpty[String]) .field(_.addresses)(sequence(addressRule))*/ diff --git a/core/src/test/scala/checklist/RuleSpec.scala b/core/src/test/scala/checklist/RuleSpec.scala index 3c311d3..aa377bb 100644 --- a/core/src/test/scala/checklist/RuleSpec.scala +++ b/core/src/test/scala/checklist/RuleSpec.scala @@ -34,28 +34,28 @@ class BaseRuleSpec extends FreeSpec with Matchers { class ConverterRulesSpec extends FreeSpec with Matchers { "parseInt" in { val rule = parseInt - rule("abc") should be(Ior.left(errors("Must be a whole number"))) - rule("123") should be(Ior.right(123)) + rule("abc") should be(Ior.left(errors("Must be a whole number"))) + rule("123") should be(Ior.right(123)) rule("123.4") should be(Ior.left(errors("Must be a whole number"))) } "parseDouble" in { val rule = parseDouble - rule("abc") should be(Ior.left(errors("Must be a number"))) - rule("123") should be(Ior.right(123.0)) + rule("abc") should be(Ior.left(errors("Must be a number"))) + rule("123") should be(Ior.right(123.0)) rule("123.4") should be(Ior.right(123.4)) } "mapValue" - { "string keys" in { - val rule = mapValue[String, String, Id]("foo") + val rule = mapValue[Id, String, String]("foo") rule(Map.empty) should be(Ior.left(errors("foo" -> "Value not found"))) rule(Map("foo" -> "bar")) should be(Ior.right("bar")) rule(Map("baz" -> "bar")) should be(Ior.left(errors("foo" -> "Value not found"))) } "int keys" in { - val rule = mapValue[Int, String, Id](123) + val rule = mapValue[Id, Int, String](123) rule(Map.empty) should be(Ior.left(errors(123 -> "Value not found"))) rule(Map(123 -> "bar")) should be(Ior.right("bar")) rule(Map(456 -> "bar")) should be(Ior.left(errors(123 -> "Value not found"))) @@ -75,91 +75,91 @@ class PropertyRulesSpec extends FreeSpec with Matchers { "non-strict" - { "eql" in { val rule = eql(0) - rule(0) should be(Ior.right(0)) + rule(0) should be(Ior.right(0)) rule(+1) should be(Ior.both(errors("Must be 0"), +1)) rule(-1) should be(Ior.both(errors("Must be 0"), -1)) } "neq" in { val rule = neq(0) - rule(0) should be(Ior.both(errors("Must not be 0"), 0)) + rule(0) should be(Ior.both(errors("Must not be 0"), 0)) rule(+1) should be(Ior.right(+1)) rule(-1) should be(Ior.right(-1)) } "lt" in { val rule = lt(0) - rule(0) should be(Ior.both(errors("Must be less than 0"), 0)) - rule(1) should be(Ior.both(errors("Must be less than 0"), 1)) + rule(0) should be(Ior.both(errors("Must be less than 0"), 0)) + rule(1) should be(Ior.both(errors("Must be less than 0"), 1)) rule(-1) should be(Ior.right(-1)) } "lte" in { val rule = lte(0) - rule(0) should be(Ior.right(0)) - rule(1) should be(Ior.both(errors("Must be less than or equal to 0"), 1)) + rule(0) should be(Ior.right(0)) + rule(1) should be(Ior.both(errors("Must be less than or equal to 0"), 1)) rule(-1) should be(Ior.right(-1)) } "gt" in { val rule = gt(0) - rule(0) should be(Ior.both(errors("Must be greater than 0"), 0)) - rule(1) should be(Ior.right(1)) + rule(0) should be(Ior.both(errors("Must be greater than 0"), 0)) + rule(1) should be(Ior.right(1)) rule(-1) should be(Ior.both(errors("Must be greater than 0"), -1)) } "gte" in { val rule = gte(0) - rule(0) should be(Ior.right(0)) - rule(1) should be(Ior.right(1)) + rule(0) should be(Ior.right(0)) + rule(1) should be(Ior.right(1)) rule(-1) should be(Ior.both(errors("Must be greater than or equal to 0"), -1)) } "nonEmpty" in { val rule = nonEmpty[String] - rule("") should be(Ior.both(errors("Must not be empty"), "")) - rule(" ") should be(Ior.right(" ")) + rule("") should be(Ior.both(errors("Must not be empty"), "")) + rule(" ") should be(Ior.right(" ")) rule(" a ") should be(Ior.right(" a ")) } - "lengthLt" in { + "lengthLt" in { val rule = lengthLt(5, errors("fail")) - rule("") should be(Ior.right("")) - rule("abcd") should be(Ior.right("abcd")) + rule("") should be(Ior.right("")) + rule("abcd") should be(Ior.right("abcd")) rule("abcde") should be(Ior.both(errors("fail"), "abcde")) } "lengthLte" in { val rule = lengthLte[String](5, errors("fail")) - rule("") should be(Ior.right("")) - rule("abcde") should be(Ior.right("abcde")) + rule("") should be(Ior.right("")) + rule("abcde") should be(Ior.right("abcde")) rule("abcdef") should be(Ior.both(errors("fail"), "abcdef")) } "lengthGt" in { val rule = lengthGt[String](1, errors("fail")) - rule("") should be(Ior.both(errors("fail"), "")) - rule("a") should be(Ior.both(errors("fail"), "a")) + rule("") should be(Ior.both(errors("fail"), "")) + rule("a") should be(Ior.both(errors("fail"), "a")) rule("ab") should be(Ior.right("ab")) } "lengthGte" in { val rule = lengthGte[String](2, errors("fail")) - rule("") should be(Ior.both(errors("fail"), "")) - rule(" ") should be(Ior.both(errors("fail"), " ")) - rule("a") should be(Ior.both(errors("fail"), "a")) + rule("") should be(Ior.both(errors("fail"), "")) + rule(" ") should be(Ior.both(errors("fail"), " ")) + rule("a") should be(Ior.both(errors("fail"), "a")) rule("ab") should be(Ior.right("ab")) } "matchesRegex" in { val rule = matchesRegex("^[^@]+@[^@]+$".r) - rule("dave@example.com") should be(Ior.right("dave@example.com")) - rule("dave@") should be(Ior.both(errors("Must match the pattern '^[^@]+@[^@]+$'"), "dave@")) - rule("@example.com") should be(Ior.both(errors("Must match the pattern '^[^@]+@[^@]+$'"), "@example.com")) + rule("dave@example.com") should be(Ior.right("dave@example.com")) + rule("dave@") should be(Ior.both(errors("Must match the pattern '^[^@]+@[^@]+$'"), "dave@")) + rule("@example.com") should be(Ior.both(errors("Must match the pattern '^[^@]+@[^@]+$'"), "@example.com")) rule("dave@@example.com") should be(Ior.both(errors("Must match the pattern '^[^@]+@[^@]+$'"), "dave@@example.com")) } @@ -185,91 +185,91 @@ class PropertyRulesSpec extends FreeSpec with Matchers { "strict" - { "eqlStrict" in { val rule = eqlStrict(0) - rule(0) should be(Ior.right(0)) + rule(0) should be(Ior.right(0)) rule(+1) should be(Ior.left(errors("Must be 0"))) rule(-1) should be(Ior.left(errors("Must be 0"))) } "neqStrict" in { val rule = neqStrict(0) - rule(0) should be(Ior.left(errors("Must not be 0"))) + rule(0) should be(Ior.left(errors("Must not be 0"))) rule(+1) should be(Ior.right(+1)) rule(-1) should be(Ior.right(-1)) } "ltStrict" in { val rule = ltStrict(0) - rule(0) should be(Ior.left(errors("Must be less than 0"))) - rule(1) should be(Ior.left(errors("Must be less than 0"))) + rule(0) should be(Ior.left(errors("Must be less than 0"))) + rule(1) should be(Ior.left(errors("Must be less than 0"))) rule(-1) should be(Ior.right(-1)) } "lteStrict" in { val rule = lteStrict(0) - rule(0) should be(Ior.right(0)) - rule(1) should be(Ior.left(errors("Must be less than or equal to 0"))) + rule(0) should be(Ior.right(0)) + rule(1) should be(Ior.left(errors("Must be less than or equal to 0"))) rule(-1) should be(Ior.right(-1)) } "gtStrict" in { val rule = gtStrict(0) - rule(0) should be(Ior.left(errors("Must be greater than 0"))) - rule(1) should be(Ior.right(1)) + rule(0) should be(Ior.left(errors("Must be greater than 0"))) + rule(1) should be(Ior.right(1)) rule(-1) should be(Ior.left(errors("Must be greater than 0"))) } "gteStrict" in { val rule = gteStrict(0) - rule(0) should be(Ior.right(0)) - rule(1) should be(Ior.right(1)) + rule(0) should be(Ior.right(0)) + rule(1) should be(Ior.right(1)) rule(-1) should be(Ior.left(errors("Must be greater than or equal to 0"))) } "nonEmptyStrict" in { val rule = nonEmptyStrict[String] - rule("") should be(Ior.left(errors("Must not be empty"))) - rule(" ") should be(Ior.right(" ")) + rule("") should be(Ior.left(errors("Must not be empty"))) + rule(" ") should be(Ior.right(" ")) rule(" a ") should be(Ior.right(" a ")) } - "lengthLt" in { + "lengthLt" in { val rule = lengthLtStrict(5, errors("fail")) - rule("") should be(Ior.right("")) - rule("abcd") should be(Ior.right("abcd")) + rule("") should be(Ior.right("")) + rule("abcd") should be(Ior.right("abcd")) rule("abcde") should be(Ior.left(errors("fail"))) } "lengthLteStrict" in { val rule = lengthLteStrict[String](5, errors("fail")) - rule("") should be(Ior.right("")) - rule("abcde") should be(Ior.right("abcde")) + rule("") should be(Ior.right("")) + rule("abcde") should be(Ior.right("abcde")) rule("abcdef") should be(Ior.left(errors("fail"))) } "lengthGtStrict" in { val rule = lengthGtStrict[String](1, errors("fail")) - rule("") should be(Ior.left(errors("fail"))) - rule("a") should be(Ior.left(errors("fail"))) + rule("") should be(Ior.left(errors("fail"))) + rule("a") should be(Ior.left(errors("fail"))) rule("ab") should be(Ior.right("ab")) } "lengthGteStrict" in { val rule = lengthGteStrict[String](2, errors("fail")) - rule("") should be(Ior.left(errors("fail"))) - rule(" ") should be(Ior.left(errors("fail"))) - rule("a") should be(Ior.left(errors("fail"))) + rule("") should be(Ior.left(errors("fail"))) + rule(" ") should be(Ior.left(errors("fail"))) + rule("a") should be(Ior.left(errors("fail"))) rule("ab") should be(Ior.right("ab")) } "matchesRegexStrict" in { val rule = matchesRegexStrict("^[^@]+@[^@]+$".r) - rule("dave@example.com") should be(Ior.right("dave@example.com")) - rule("dave@") should be(Ior.left(errors("Must match the pattern '^[^@]+@[^@]+$'"))) - rule("@example.com") should be(Ior.left(errors("Must match the pattern '^[^@]+@[^@]+$'"))) + rule("dave@example.com") should be(Ior.right("dave@example.com")) + rule("dave@") should be(Ior.left(errors("Must match the pattern '^[^@]+@[^@]+$'"))) + rule("@example.com") should be(Ior.left(errors("Must match the pattern '^[^@]+@[^@]+$'"))) rule("dave@@example.com") should be(Ior.left(errors("Must match the pattern '^[^@]+@[^@]+$'"))) } @@ -297,12 +297,12 @@ class CombinatorRulesSpec extends FreeSpec with Matchers { "map" in { val rule = Rule.gt[Int](0, errors("fail")).map(_ + "!") rule(+1) should be(Ior.right("1!")) - rule( 0) should be(Ior.both(errors("fail"), "0!")) + rule(0) should be(Ior.both(errors("fail"), "0!")) rule(-1) should be(Ior.both(errors("fail"), "-1!")) } "emap" in { - val rule = Rule.gt[Int](0, errors("fail")).emap(x => if(x % 2 == 0) Ior.right(x) else Ior.left(errors("mapFail"))) + val rule = Rule.gt[Int](0, errors("fail")).emap(x => if (x % 2 == 0) Ior.right(x) else Ior.left(errors("mapFail"))) rule(-1) should be(Ior.left(errors("fail", "mapFail"))) rule(0) should be(Ior.both(errors("fail"), 0)) @@ -327,18 +327,18 @@ class CombinatorRulesSpec extends FreeSpec with Matchers { "contramap" in { val rule = Rule.gt[Int](0, errors("fail")).contramap[Int](_ + 1) rule(+1) should be(Ior.right(2)) - rule( 0) should be(Ior.right(1)) + rule(0) should be(Ior.right(1)) rule(-1) should be(Ior.both(errors("fail"), 0)) } "contramapPath" in { - val rule: Rule[Int, Int, Id] = Rule.gt[Int](0, errors("fail")).contramapPath("n")(_ + 1) + val rule: Rule[Id, Int, Int] = Rule.gt[Int](0, errors("fail")).contramapPath("n")(_ + 1) rule(+1) should be(Ior.right(2)) - rule( 0) should be(Ior.right(1)) + rule(0) should be(Ior.right(1)) rule(-1) should be(Ior.both(errors("fail").map(_.prefix("n")), 0)) } - /*"flatMap" in { + "flatMap" in { val rule = for { a <- gte[Int](0, warnings("Should be >= 0")) b <- if(a > 0) { @@ -350,34 +350,34 @@ class CombinatorRulesSpec extends FreeSpec with Matchers { rule( 0) should be(Ior.right(0)) rule( 10) should be(Ior.both(errors("Must be < 10"), 10)) rule(-10) should be(Ior.both(warnings("Should be >= 0") concatNel errors("Must be > -10"), -10)) - }*/ + } - /*"andThen" in { + "andThen" in { val rule = parseInt andThen gt(0) rule("abc") should be(Ior.left(errors("Must be a whole number"))) rule( "-1") should be(Ior.both(errors("Must be greater than 0"), -1)) rule( "0") should be(Ior.both(errors("Must be greater than 0"), 0)) rule( "+1") should be(Ior.right(1)) rule("1.2") should be(Ior.left(errors("Must be a whole number"))) - }*/ + } - /*"zip" in { + "zip" in { val rule = parseInt zip parseDouble rule("abc") should be(Ior.left(errors("Must be a whole number", "Must be a number"))) rule( "1") should be(Ior.right((1, 1.0))) rule("1.2") should be(Ior.left(errors("Must be a whole number"))) - }*/ + } - /*"prefix" in { + "prefix" in { val rule = parseInt andThen gt(0) prefix "num" rule("abc") should be(Ior.left(errors("num" -> "Must be a whole number"))) rule( "-1") should be(Ior.both(errors("num" -> "Must be greater than 0"), -1)) rule( "0") should be(Ior.both(errors("num" -> "Must be greater than 0"), 0)) rule( "+1") should be(Ior.right(1)) rule("1.2") should be(Ior.left(errors("num" -> "Must be a whole number"))) - }*/ + } - /*"composeLens" in { + "composeLens" in { val l = monocle.Lens[(Int, Int), Int](p => p._1)(n => p => (n, p._2)) val r = monocle.Lens[(Int, Int), Int](p => p._2)(n => p => (p._1, n)) val rule = (lt(0) composeLens l) andThen (gt(0) composeLens r) @@ -386,9 +386,9 @@ class CombinatorRulesSpec extends FreeSpec with Matchers { rule((-1, -1)) should be(Ior.both(errors("Must be greater than 0"), (-1, -1))) rule((+1, -1)) should be(Ior.both(errors("Must be less than 0", "Must be greater than 0"), (1, -1))) rule((-1, +1)) should be(Ior.right((-1, 1))) - }*/ + } - /*"at" in { + "at" in { val l = monocle.Lens[(Int, Int), Int](p => p._1)(n => p => (n, p._2)) val r = monocle.Lens[(Int, Int), Int](p => p._2)(n => p => (p._1, n)) val rule = lt(0).at("l", l) andThen gt(0).at("r", r) @@ -397,7 +397,7 @@ class CombinatorRulesSpec extends FreeSpec with Matchers { rule((-1, -1)) should be(Ior.both(errors("r" -> "Must be greater than 0"), (-1, -1))) rule((+1, -1)) should be(Ior.both(errors("l" -> "Must be less than 0", "r" -> "Must be greater than 0"), (1, -1))) rule((-1, +1)) should be(Ior.right((-1, 1))) - }*/ + } } class CollectionRuleSpec extends FreeSpec with Matchers { @@ -413,37 +413,37 @@ class CollectionRuleSpec extends FreeSpec with Matchers { "optional" in { val rule = optional(eql("ok")) - rule(None) should be(Ior.right(None)) + rule(None) should be(Ior.right(None)) rule(Some("ok")) should be(Ior.right(Some("ok"))) rule(Some("no")) should be(Ior.both(errors("Must be ok"), Some("no"))) } "required" in { val rule = required(eql(0)) - rule(None) should be(Ior.left(errors("Value is required"))) + rule(None) should be(Ior.left(errors("Value is required"))) rule(Some(0)) should be(Ior.right(0)) rule(Some(1)) should be(Ior.both(errors("Must be 0"), 1)) } } class CatsRuleSpec extends FreeSpec with Matchers { + import cats.data._ -// import cats.syntax.all._ + import cats.syntax.all._ case class Address(house: Int, street: String) - def getField(name: String): Rule[Map[String, String], String, Id] = - pure[Map[String, String], String, Id](_.get(name).map(Ior.right).getOrElse(Ior.left(errors(s"Field not found: ${name}")))) - - val parseAddress = - ((getField("house") andThen parseInt andThen gt(0)), (getField("street") andThen nonEmpty)) - .mapN (Address.apply) + def getField(name: String): Rule[Id, Map[String, String], String] = + pure[Id, Map[String, String], String](_.get(name).map(Ior.right).getOrElse(Ior.left(errors(s"Field not found: ${name}")))) + val parseAddress = + (getField("house") andThen parseInt andThen gt(0), getField("street") andThen nonEmpty) + .mapN(Address.apply) def runTests(f: Map[String, String] => Checked[Address]) = { "good data" in { val actual = parseAddress(Map( - "house" -> "29", + "house" -> "29", "street" -> "Acacia Road" )) val expected = Ior.right(Address(29, "Acacia Road")) @@ -451,13 +451,13 @@ class CatsRuleSpec extends FreeSpec with Matchers { } "empty data" in { - val actual = parseAddress(Map.empty[String, String]) + val actual = parseAddress(Map.empty[String, String]) val expected = Ior.left(errors("Field not found: house", "Field not found: street")) actual should be(expected) } "bad fields" in { - val actual = parseAddress(Map("house" -> "-1", "street" -> "")) + val actual = parseAddress(Map("house" -> "-1", "street" -> "")) val expected = Ior.both(errors("Must be greater than 0", "Must not be empty"), Address(-1, "")) actual should be(expected) } @@ -476,14 +476,16 @@ class CatsRuleSpec extends FreeSpec with Matchers { } class Rule1SyntaxSpec extends FreeSpec with Matchers { + @Lenses case class Coord(x: Int, y: Int) case class Bar(baz: Int) + case class Foo(bar: Bar) "field macro" in { - val rule = Rule[Coord, Id] -// .field(_.x)(gt(0, errors("fail"))) + val rule = Rule[Id, Coord] + // .field(_.x)(gt(0, errors("fail"))) rule(Coord(1, 0)) should be(Ior.right(Coord(1, 0))) rule(Coord(0, 0)) should be(Ior.both(errors(("x" :: PNil) -> "fail"), Coord(0, 0))) rule(Coord(0, 1)) should be(Ior.both(errors(("x" :: PNil) -> "fail"), Coord(0, 1))) @@ -491,15 +493,15 @@ class Rule1SyntaxSpec extends FreeSpec with Matchers { } "field macro with multi-level accessor" in { - val rule = Rule[Foo, Id] -// .field(_.bar.baz)(gt(0, errors("fail"))) + val rule = Rule[Id, Foo] + // .field(_.bar.baz)(gt(0, errors("fail"))) rule(Foo(Bar(1))) should be(Ior.right(Foo(Bar(1)))) rule(Foo(Bar(0))) should be(Ior.both(errors(("bar" :: "baz" :: PNil) -> "fail"), Foo(Bar(0)))) } "fieldWith macro" in { - val rule = Rule[Coord, Id] -// .fieldWith(_.x)(c => gte(c.y, errors("fail"))) + val rule = Rule[Id, Coord] + // .fieldWith(_.x)(c => gte(c.y, errors("fail"))) rule(Coord(1, 0)) should be(Ior.right(Coord(1, 0))) rule(Coord(0, 0)) should be(Ior.right(Coord(0, 0))) rule(Coord(0, 1)) should be(Ior.both(errors(("x" :: PNil) -> "fail"), Coord(0, 1))) @@ -507,8 +509,8 @@ class Rule1SyntaxSpec extends FreeSpec with Matchers { } "field method" in { - val rule = Rule[Coord, Id] -// .field("z" :: PNil, Coord.x)(gt(0, errors("fail"))) + val rule = Rule[Id, Coord] + // .field("z" :: PNil, Coord.x)(gt(0, errors("fail"))) rule(Coord(1, 0)) should be(Ior.right(Coord(1, 0))) rule(Coord(0, 0)) should be(Ior.both(errors(("z" :: PNil) -> "fail"), Coord(0, 0))) rule(Coord(0, 1)) should be(Ior.both(errors(("z" :: PNil) -> "fail"), Coord(0, 1))) @@ -516,8 +518,8 @@ class Rule1SyntaxSpec extends FreeSpec with Matchers { } "fieldWith method" in { - val rule = Rule[Coord, Id] -// .fieldWith("z" :: PNil, Coord.x)(c => gte(c.y, errors("fail"))) + val rule = Rule[Id, Coord] + // .fieldWith("z" :: PNil, Coord.x)(c => gte(c.y, errors("fail"))) rule(Coord(1, 0)) should be(Ior.right(Coord(1, 0))) rule(Coord(0, 0)) should be(Ior.right(Coord(0, 0))) rule(Coord(0, 1)) should be(Ior.both(errors(("z" :: PNil) -> "fail"), Coord(0, 1))) diff --git a/core/src/test/scala/checklist/laws/RuleLawTests.scala b/core/src/test/scala/checklist/laws/RuleLawTests.scala index a20c359..947a58d 100644 --- a/core/src/test/scala/checklist/laws/RuleLawTests.scala +++ b/core/src/test/scala/checklist/laws/RuleLawTests.scala @@ -1,11 +1,12 @@ package checklist.laws //import cats.laws.discipline.arbitrary._ -//import cats.laws.discipline.{ApplicativeTests, ProfunctorTests} +//import cats.Id +//import cats.laws.discipline.ApplicativeTests import cats.tests.CatsSuite //import checklist._ class RuleLawTests extends CatsSuite { -// checkAll("Rule[Int, String]", ApplicativeTests[Rule[Int, ?]].applicative[String, String, String]) +// checkAll("Rule[String, Id, Int]", ApplicativeTests[Rule[Int, ?, Id]](Rule.ruleApplicative[Int, Id]).applicative[String, String, String]) // checkAll("Rule[Int, String]", ProfunctorTests[Rule].profunctor[Int, Int, Int, String, String, String]) } diff --git a/core/src/test/scala/checklist/laws/package.scala b/core/src/test/scala/checklist/laws/package.scala index c94a60a..443027c 100644 --- a/core/src/test/scala/checklist/laws/package.scala +++ b/core/src/test/scala/checklist/laws/package.scala @@ -1,27 +1,27 @@ package checklist -import cats.{Applicative/*, Eq*/} +import cats.{/*Applicative, */Id} import cats.data.{Ior, NonEmptyList} import cats.implicits._ import cats.laws.discipline.arbitrary._ //import cats.laws.discipline.eq._ import org.scalacheck.Arbitrary -import scala.language.higherKinds +//import scala.language.higherKinds package object laws extends CatsInstances with ScalacheckInstances trait ScalacheckInstances { - implicit def arbRule[A: Arbitrary, B: Arbitrary, F[_] : Applicative](implicit arbF: Arbitrary[A => Ior[A, B]]): Arbitrary[Rule[A, B, F]] = Arbitrary( + implicit def arbRule[A: Arbitrary, B: Arbitrary](implicit arbF: Arbitrary[A => Ior[A, B]]): Arbitrary[Rule[Id, A, B]] = Arbitrary( for { f <- arbF.arbitrary messages <- Arbitrary.arbitrary[Messages] } yield { - Rule.pure(in => Applicative[F].pure(f(in).fold( + Rule.pure[Id, A, B](in => f(in).fold( _ => Ior.left(messages), Ior.right(_), (_, b) => Ior.both(messages, b) - ))) + )) } ) @@ -51,6 +51,6 @@ trait ScalacheckInstances { } trait CatsInstances { -// implicit def ruleEq[A: Arbitrary, B: Eq, F[_] : Applicative]: Eq[Rule[A, B, F]] = -// catsLawsEqForFn1[A, Checked[B]].contramap[Rule[A, B, F]](rule => rule.apply _) +// implicit def ruleEq[A: Arbitrary, B: Eq, F[_] : Applicative]: Eq[Rule[B, F, A]] = +// catsLawsEqForFn1[A, Checked[B]].contramap[Rule[B, F, A]](rule => rule.apply _) } diff --git a/refinement/src/main/scala/checklist/refinement/RuleHListSyntax.scala b/refinement/src/main/scala/checklist/refinement/RuleHListSyntax.scala index 3aea7f4..c666d74 100644 --- a/refinement/src/main/scala/checklist/refinement/RuleHListSyntax.scala +++ b/refinement/src/main/scala/checklist/refinement/RuleHListSyntax.scala @@ -7,7 +7,7 @@ import cats.data.Ior import checklist.{Rule, PathPrefix} trait RuleHListSyntax { - +/* implicit class RuleHList[A, B <: HList, Rev <: HList](rule: Rule[A, B])(implicit reverse: Reverse.Aux[B, Rev]) { self => /** @@ -91,5 +91,5 @@ trait RuleHListSyntax { * @tparam A The type to be validated */ def builder[A]: Rule[A, HNil] = Rule.pass[A].map(_ => HNil) - } + }*/ } diff --git a/refinement/src/test/scala/checklist/refinement/RuleHListSyntaxSpec.scala b/refinement/src/test/scala/checklist/refinement/RuleHListSyntaxSpec.scala index ae9420c..e602f13 100644 --- a/refinement/src/test/scala/checklist/refinement/RuleHListSyntaxSpec.scala +++ b/refinement/src/test/scala/checklist/refinement/RuleHListSyntaxSpec.scala @@ -5,7 +5,7 @@ import checklist.Message._ import org.scalatest._ import cats.data.{NonEmptyList, Ior} -class RuleBuilderSpec extends FreeSpec with Matchers with RuleHListSyntax { +class RuleBuilderSpec extends FreeSpec with Matchers with RuleHListSyntax {/* case class RawFoo(positive: Int, potentiallyEmptyList: List[String], untrimmed: String) @@ -117,4 +117,4 @@ class RuleBuilderSpec extends FreeSpec with Matchers with RuleHListSyntax { rule(Foo(5)) should be(Ior.Right(Bar(5))) } } -} +*/}