no.kodeworks.kvarg.check.Check.scala Maven / Gradle / Ivy
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