All Downloads are FREE. Search and download functionalities are using the official Maven repository.

no.kodeworks.kvarg.check.Check.scala Maven / Gradle / Ivy

There is a newer version: 0.7
Show newest version
package no.kodeworks.kvarg

import cats.syntax.option._
import no.kodeworks.kvarg.check.Check.defaults.MkDefaults
import no.kodeworks.kvarg.patch.{Patch, Poption, Ppatch, Pstring, Pvalue}
import no.kodeworks.kvarg.refs.Refs
import no.kodeworks.kvarg.util.{hlistToList, listToHList}
import shapeless._
import shapeless.ops.hlist.{Fill, Intersection, Length, Mapped, Mapper, RemoveAll, Zip, ZipWithKeys}
import shapeless.ops.record.{SelectAll, UnzipFields}

import scala.language.experimental.macros
import scala.language.{dynamics, implicitConversions}

package object check {

  sealed trait CheckedT {
    val valid: Boolean
    val messages: List[String]
  }

  case class Checked(path: List[String] = Nil, valid: Boolean = true, messages: List[String] = Nil) extends CheckedT

  case class CheckedNoPath(valid: Boolean = true, messages: List[String] = Nil) extends CheckedT

  sealed trait Check[C] {
    def &&(cc: Check[C]): Check[C] = CheckAnd(this, cc)

    def ||(cc: Check[C]): Check[C] = CheckOr(this, cc)

    def apply(c: C, path: Option[String]): List[Checked]

    def apply(c: C): List[Checked] = apply(c, None)

    def apply(c: Patch[C],
              includeMissing: Boolean = false,
              path: Option[String] = None): List[Checked] = Nil

    //TODO when merging this with either of the applies above, duplicates may arise
    def apply(r: Refs[C]): List[Checked] =
      r.asMap.toList.collect {
        case (key, None) => Checked(List(key.name), false, List("Ref not found"))
      }
  }

  case class CheckOne[C](func: C => Boolean, msg: String = "") extends Check[C] {
    override def &&(cc: Check[C]) =
      CheckAnd(this, cc)

    override def apply(c: C, path: Option[String]): List[Checked] = {
      val r = func(c)
      if (!r) List(Checked(path.toList, r, List(msg)))
      else List(Checked(path = path.toList))
    }
  }

  case class CheckAnd[C](cc0: Check[C], cc1: Check[C]) extends Check[C] {
    override def apply(c: C, path: Option[String]): List[Checked] = {
      val c0 = cc0(c, path)
      val c1 = cc1(c, path)
      List(Checked(path.toList,
        c0.forall(_.valid) && c1.forall(_.valid),
        (c0.flatMap(_.messages) ++ c1.flatMap(_.messages)).distinct
      ))
    }

    override def apply(c: Patch[C],
                       includeMissing: Boolean = false,
                       path: Option[String] = None): List[Checked] = {
      val c0 = cc0(c, includeMissing, path)
      val c1 = cc1(c, includeMissing, path)
      List(Checked(path.toList,
        c0.forall(_.valid) && c1.forall(_.valid),
        (c0.flatMap(_.messages) ++ c1.flatMap(_.messages)).distinct
      ))
    }
  }

  case class CheckOr[C](cc0: Check[C], cc1: Check[C]) extends Check[C] {
    override def apply(c: C, path: Option[String]): List[Checked] = {
      val c0 = cc0(c, path)
      if (c0.forall(_.valid)) c0
      else {
        val c1 = cc1(c, path)
        if (c1.forall(_.valid)) c1
        else CheckAnd(cc0, cc1)(c, path)
      }
    }

    override def apply(c: Patch[C],
                       includeMissing: Boolean = false,
                       path: Option[String] = None): List[Checked] = {
      val c0 = cc0(c, includeMissing, path)
      if (c0.forall(_.valid)) c0
      else {
        val c1 = cc1(c, includeMissing, path)
        if (c1.forall(_.valid)) c1
        else CheckAnd(cc0, cc1)(c, includeMissing, path)
      }
    }
  }

  object Check {

    object check extends Poly1 {
      implicit def apply[S <: Symbol, C, CC <: Check[C]] = at[(S, C, CC)] {
        case (key, c, check) =>
          check(c, Some(key.name))
      }
    }

    object patchCoprodCheck extends Poly1 {
      implicit def apply[S <: Symbol, C, CC <: Check[C]]
      : Case.Aux[(S, Boolean, Poption[C], CC), Option[List[Checked]]]
      = at[(S, Boolean, Poption[C], CC)] {
        case (key, includeMissing, pcc, check) =>
          pcc match {
            case Pvalue(p) => check(p, key.name.some).some
            case Ppatch(pp) => check(pp, includeMissing, key.name.some).some
            case Pstring(_) =>
              List(Checked(List(key.name), false, List("Invalid type"))).some
            case _ if (includeMissing) =>
              List(Checked(List(key.name), false, List("Required field"))).some
            case _ =>
              None
          }
      }
    }

    object defaults {
      implicit def apply[Checkable](implicit mkDefaults: MkDefaults[Checkable]): Check[Checkable] =
        mkDefaults.check

      implicit def mkDefaults[
      Checkable
      , CheckableFields <: HList
      , CheckableKeys <: HList
      , CheckableValues <: HList
      , CheckableValuesCoprod <: HList
      , CheckableValuesCheck <: HList
      , CheckableCoprod <: HList
      , CheckableLength <: Nat
      , CheckableCheck <: HList
      , KeysIntersect <: HList
      , KeysIntersectLength <: Nat
      , SpecCheckable <: HList
      , SpecCheckableZip <: HList
      , Checkeds <: HList
      , CheckableCheckCoprodZip <: HList
      , CoprodCheckeds <: HList
      , IncludeMissing <: HList
      ]
      (implicit
       checkableLG: LabelledGeneric.Aux[Checkable, CheckableFields]
       , checkableUnzip: UnzipFields.Aux[CheckableFields, CheckableKeys, CheckableValues]
       , checkableValuesToCoprod: Mapped.Aux[CheckableValues, Poption, CheckableValuesCoprod]
       , checkableCoprodZip: ZipWithKeys.Aux[CheckableKeys, CheckableValuesCoprod, CheckableCoprod]
       , checkableCoprodUnzip: UnzipFields.Aux[CheckableCoprod, CheckableKeys, CheckableValuesCoprod]
       , checkableLength: Length.Aux[CheckableKeys, CheckableLength]
       , specValues: Strict[DefaultChecks.Aux[CheckableValues, CheckableValuesCheck]]
       , checkableZip: Zip.Aux[CheckableKeys :: CheckableValues :: CheckableValuesCheck :: HNil, SpecCheckableZip]
       , checkeds: Mapper.Aux[check.type, SpecCheckableZip, Checkeds]
       , includeMissingFill: Fill.Aux[CheckableLength, Boolean, IncludeMissing]
       , checkableCheckCoprodZip: Zip.Aux[CheckableKeys :: IncludeMissing :: CheckableValuesCoprod :: CheckableValuesCheck :: HNil, CheckableCheckCoprodZip]
       , coprodCheckeds: Mapper.Aux[patchCoprodCheck.type, CheckableCheckCoprodZip, CoprodCheckeds]
      )
      : MkDefaults[Checkable] = {
        new MkDefaults[Checkable](
          new Check[Checkable] {
            override def apply(checkable: Checkable, path: Option[String]): List[Checked] = {
              val checkableFields = checkableLG.to(checkable)
              val specCheckableZip0 = checkableZip(checkableUnzip.keys() :: checkableUnzip.values(checkableFields) :: specValues.value() :: HNil)
              val checkeds0 = checkeds(specCheckableZip0)
              val checkedList0 = hlistToList[List[Checked]](checkeds0).flatten.map { c =>
                c.copy(path = path.toList ++ c.path)
              }
              checkedList0
            }

            override def apply(
                                checkable: Patch[Checkable],
                                includeMissing: Boolean,
                                path: Option[String]): List[Checked] = {
              val checkableCoprod = listToHList[CheckableCoprod](checkable.patcher.keys.map(checkable.poptions(_)))
              val checkableCheckValues0 = specValues.value()
              val checkableValuesCoprod0 = checkableCoprodUnzip.values(checkableCoprod)
              val includeMissing0 = includeMissingFill(includeMissing)
              val checkableCheckCoprodZip0 = checkableCheckCoprodZip(checkableUnzip.keys() :: includeMissing0 :: checkableValuesCoprod0 :: checkableCheckValues0 :: HNil)
              val coprodCheckeds0 = coprodCheckeds(checkableCheckCoprodZip0)
              val coprodCheckedList0 = hlistToList[Option[List[Checked]]](coprodCheckeds0).flatten.flatten.map { c =>
                c.copy(path = path.toList ++ c.path)
              }
              coprodCheckedList0
            }
          }
        )
      }

      class MkDefaults[Checkable](val check: Check[Checkable])

    }

    //TODO make compiler happier with the return value of apply
    def apply[Checkable](func: Checkable => Boolean, msg: String) = new CheckOne[Checkable](func, msg)

    def apply[Checkable]: MkCheck[Checkable] = new MkCheck[Checkable]

    class MkCheck[Checkable] extends Dynamic {
      def applyRecord[
      CheckableFields <: HList
      , CheckableKeys <: HList
      , CheckableValues <: HList
      , CheckableValuesCoprod <: HList
      , CheckableValuesCheck <: HList
      , CheckableCoprod <: HList
      , CheckableLength <: Nat
      , Spec <: HList
      , SpecKeys <: HList
      , SpecValues <: HList
      , NotSpecKeys <: HList
      , NotSpecCheckableValues <: HList
      , NotSpecValues <: HList
      , NotSpec <: HList
      , CheckableCheck <: HList
      , KeysIntersect <: HList
      , KeysIntersectLength <: Nat
      , SpecCheckable <: HList
      , SpecCheckableZip <: HList
      , Checkeds <: HList
      , CheckableCheckCoprodZip <: HList
      , CoprodCheckeds <: HList
      , IncludeMissing <: HList
      ](spec: Spec)
       (implicit
        checkableLG: LabelledGeneric.Aux[Checkable, CheckableFields]
        , checkableUnzip: UnzipFields.Aux[CheckableFields, CheckableKeys, CheckableValues]
        , checkableValuesToCoprod: Mapped.Aux[CheckableValues, Poption, CheckableValuesCoprod]
        , checkableValuesToCheck: Mapped.Aux[CheckableValues, Check, CheckableValuesCheck]
        , checkableCoprodZip: ZipWithKeys.Aux[CheckableKeys, CheckableValuesCoprod, CheckableCoprod]
        , checkableCoprodUnzip: UnzipFields.Aux[CheckableCoprod, CheckableKeys, CheckableValuesCoprod]
        , checkableLength: Length.Aux[CheckableKeys, CheckableLength]
        , specUnzip: UnzipFields.Aux[Spec, SpecKeys, SpecValues]
        , keysIntersect: Intersection.Aux[SpecKeys, CheckableKeys, KeysIntersect]
        , keysIntersectLength: Length.Aux[KeysIntersect, KeysIntersectLength]
        , keysLength: Length.Aux[Spec, KeysIntersectLength]
        , specKeysRemoveAll: RemoveAll.Aux[CheckableKeys, SpecKeys, (SpecKeys, NotSpecKeys)]
        , notSpecCheckableValues: SelectAll.Aux[CheckableFields, NotSpecKeys, NotSpecCheckableValues]
        , notSpecTypeChecks: DefaultChecks.Aux[NotSpecCheckableValues, NotSpecValues]
        , notSpec: ZipWithKeys.Aux[NotSpecKeys, NotSpecValues, NotSpec]
        , checkableCheckZipWithKeys: ZipWithKeys.Aux[CheckableKeys, CheckableValuesCheck, CheckableCheck]
        , checkableCheckValues: UnzipFields.Aux[CheckableCheck, CheckableKeys, CheckableValuesCheck]
        , checkableCheckRemoveAll: RemoveAll.Aux[CheckableCheck, Spec, (Spec, NotSpec)]
        , specCheckable: SelectAll.Aux[CheckableFields, SpecKeys, SpecCheckable]
        , specCheckableZip: Zip.Aux[SpecKeys :: SpecCheckable :: SpecValues :: HNil, SpecCheckableZip]
        , checkeds: Mapper.Aux[check.type, SpecCheckableZip, Checkeds]
        , includeMissingFill: Fill.Aux[CheckableLength, Boolean, IncludeMissing]
        , checkableCheckCoprodZip: Zip.Aux[CheckableKeys :: IncludeMissing :: CheckableValuesCoprod :: CheckableValuesCheck :: HNil, CheckableCheckCoprodZip]
        , coprodCheckeds: Mapper.Aux[patchCoprodCheck.type, CheckableCheckCoprodZip, CoprodCheckeds]
       )
      : Check[Checkable] = {
        new Check[Checkable] {
          override def apply(checkable: Checkable, path: Option[String]): List[Checked] = {
            val specCheckable0 = specCheckable(checkableLG.to(checkable))
            val specCheckableZip0 = specCheckableZip(specUnzip.keys() :: specCheckable0 :: specUnzip.values(spec) :: HNil)
            val checkeds0 = checkeds(specCheckableZip0)
            val checkedList0 = hlistToList[List[Checked]](checkeds0).flatten.map { c =>
              c.copy(path = path.toList ++ c.path)
            }
            checkedList0
          }

          override def apply(
                              checkable: Patch[Checkable],
                              includeMissing: Boolean,
                              path: Option[String]): List[Checked] = {
            val checkableCoprod = listToHList[CheckableCoprod](checkable.patcher.keys.map(checkable.poptions(_)))
            val notSpecTypeChecks0 = notSpecTypeChecks()
            val notSpec0 = notSpec(notSpecTypeChecks0)
            val checkableCheckRemoveAll0 = checkableCheckRemoveAll.reinsert((spec, notSpec0))
            val checkableCheckValues0 = checkableCheckValues.values(checkableCheckRemoveAll0)
            val checkableValuesCoprod0 = checkableCoprodUnzip.values(checkableCoprod)
            val includeMissing0 = includeMissingFill(includeMissing)
            val checkableCheckCoprodZip0 = checkableCheckCoprodZip(checkableUnzip.keys() :: includeMissing0 :: checkableValuesCoprod0 :: checkableCheckValues0 :: HNil)
            val coprodCheckeds0 = coprodCheckeds(checkableCheckCoprodZip0)
            val coprodCheckedList0 = hlistToList[Option[List[Checked]]](coprodCheckeds0).flatten.flatten.map { c =>
              c.copy(path = path.toList ++ c.path)
            }
            coprodCheckedList0
          }
        }
      }

      def applyDynamicNamed(method: String)(rec: Any*): Check[Checkable] = macro RecordMacros.forwardNamedImpl
    }

    trait DefaultChecks[L <: HList] extends DepFn0 with Serializable {
      type Out <: HList
    }

    object DefaultChecks {
      type Aux[L <: HList, Out0 <: HList] = DefaultChecks[L] {type Out = Out0}

      def apply[L <: HList](implicit defaultChecks: DefaultChecks[L]): defaultChecks.type = defaultChecks

      implicit def hnilDefaultChecks[L <: HNil]: Aux[L, HNil] = new DefaultChecks[L] {
        type Out = HNil

        def apply(): Out = HNil
      }

      implicit def hlistDefaultChecks[Checkable, Rest <: HList]
      (implicit
       defaultChecksRest: DefaultChecks[Rest]
       , defaultCheck: MkDefaults[Checkable] = null
      ) = new DefaultChecks[Checkable :: Rest] {
        type Out = Check[Checkable] :: defaultChecksRest.Out

        def apply(): Out = {
          if (null == defaultCheck) {
            CheckOne((_: Checkable) => true): Check[Checkable]
          } else {
            defaultCheck.check
          }
        } :: defaultChecksRest()
      }
    }

  }

  object syntax {

    class CheckedOps(cs: List[Checked]) {
      def asMap: Map[String, CheckedNoPath] =
        cs.map { c =>
          c.path.mkString(".") ->
            CheckedNoPath(c.valid, c.messages)
        }.toMap

      def valid: Boolean = cs.forall(_.valid)

      //TODO check of Refs and Checks needs to be merged
      def merge(checks: List[Checked]): List[Checked] = ???
    }

    implicit def checkedOps(cs: List[Checked]) = new CheckedOps(cs)

    implicit def checkedOps(cs: Map[String, CheckedNoPath]) =
      new CheckedOps(
        cs.map { case (path, checked) =>
          Checked(path.split(".").toList, checked.valid, checked.messages)
        }.toList)
  }

  //TODO move to lib
  object checks {

    object IsDigits {
      def apply(msg: String = "Only digits allowed"): Check[String] =
        Check((_: String).forall(_.isDigit), msg)
    }

  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy