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

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