io.hireproof.structure.Path.scala Maven / Gradle / Ivy
The newest version!
package io.hireproof.structure
import cats.Invariant
import cats.data.Chain.==:
import cats.data.{Chain, Validated}
import cats.syntax.all._
import io.hireproof.screening.{Validation, Violation, Violations}
import scala.annotation.tailrec
sealed abstract class Path[A] extends Structure.Product[A] { self =>
final override type Self[a] = Path[a]
final override type Element[a] = Parameter[a]
final override type Group[a] = Path[a]
def toChain: Chain[String |+| Parameter[_]]
final override def zipAll[T](path: Path[T]): Path[A |*| T] = new Path[A |*| T] {
override def toChain: Chain[String |+| Parameter[_]] = self.toChain ++ path.toChain
override def fromStringChainWithRemainder(value: Chain[String]): Validated[Errors, (Chain[String], A |*| T)] =
self.fromStringChainWithRemainder(value).andThen { case (remainder, a) =>
path.fromStringChainWithRemainder(remainder).map(_.tupleLeft(a))
}
override def toStringChain(at: A |*| T): Chain[String] =
self.toStringChain(at._1) ++ path.toStringChain(at._2)
}
override def zip[T](parameter: Parameter[T]): Path[A |*| T] = this zipAll Path.fromParameter(parameter)
final def zipParameter[T](parameter: Parameter[T]): Path[A |*| T] = zip(parameter)
final def /[T](parameter: Parameter[T])(implicit evidence: Evidence.Product.Merger[A |*| T]): Path[evidence.Out] =
zipParameter(parameter).merge(evidence)
final def zipSegment(name: String): Path[A] = (this zipAll Path.fromSegment(name)).imap(_._1)((_, ()))
final def /(name: String): Path[A] = zipSegment(name)
override private[structure] def vimap[T](
f: A => Validated[Errors, T],
g: T => A,
validation: Option[Validation[_, _]]
): Path[T] = new Path[T] {
override def toChain: Chain[String |+| Parameter[_]] = self.toChain
override def fromStringChainWithRemainder(path: Chain[String]): Validated[Errors, (Chain[String], T)] =
self.fromStringChainWithRemainder(path).andThen(_.traverse(f))
override def toStringChain(t: T): Chain[String] = self.toStringChain(g(t))
}
final def matches(path: Chain[String]): Boolean = Path.matches(path, toChain)
final def fromStringChain(path: Chain[String]): Validated[Errors, A] =
fromStringChainWithRemainder(path).andThen {
case (Chain.nil, a) => a.valid
case (remainder, _) =>
Errors.Validations(Violations.rootNel(Violation.invalid(actual = Path.print(remainder)))).invalid
}
def fromStringChainWithRemainder(path: Chain[String]): Validated[Errors, (Chain[String], A)]
def toStringChain(a: A): Chain[String]
final def toString(a: A): String = Path.print(toStringChain(a))
}
object Path {
private[structure] def print(path: Chain[String]): String = path.mkString_("/", "/", "")
@tailrec
private[structure] def matches(path: Chain[String], segments: Chain[String |+| Parameter[_]]): Boolean =
(path, segments) match {
case (head ==: path, Left(segment) ==: segments) if head === segment => matches(path, segments)
case (_ ==: path, Right(_) ==: segments) => matches(path, segments)
case (Chain.nil, Chain.nil) => true
case _ => false
}
val Empty: Path[Unit] = new Path[Unit] {
override def toChain: Chain[String |+| Parameter[_]] = Chain.empty
override def fromStringChainWithRemainder(path: Chain[String]): Validated[Errors, (Chain[String], Unit)] =
(path, ()).valid
override def toStringChain(a: Unit): Chain[String] = Chain.empty
}
def fromSegment(name: String): Path[Unit] = new Path[Unit] {
override def toChain: Chain[String |+| Parameter[_]] = Chain.one(name.asLeft)
override def fromStringChainWithRemainder(path: Chain[String]): Validated[Errors, (Chain[String], Unit)] =
path match {
case head ==: tail =>
Validated.cond(name === head, (tail, ()), Errors.Validations(Violations.rootNel(Violation.invalid(head))))
case _ => Errors.Validations(Violations.rootNel(Violation.missing(reference = name))).invalid
}
override def toStringChain(a: Unit): Chain[String] = Chain.one(name)
}
def fromParameter[A](parameter: Parameter[A]): Path[A] = new Path[A] {
override def toChain: Chain[String |+| Parameter[_]] = Chain.one(parameter.asRight)
override def fromStringChainWithRemainder(path: Chain[String]): Validated[Errors, (Chain[String], A)] =
path match {
case head ==: tail => parameter.fromString(head).bimap(_.modifyHistory(parameter.name /: _), (tail, _))
case _ => Errors.Validations(Violations.rootNel(Violation.missing(parameter.name))).invalid
}
override def toStringChain(value: A): Chain[String] = Chain.one(parameter.toString(value))
}
implicit val invariant: Invariant[Path] = new Invariant[Path] {
override def imap[A, B](fa: Path[A])(f: A => B)(g: B => A): Path[B] = fa.imap(f)(g)
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy