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

io.hireproof.structure.Output.scala Maven / Gradle / Ivy

The newest version!
package io.hireproof.structure

import cats.Invariant
import cats.data.{Chain, Validated}
import cats.syntax.all._
import io.hireproof.screening.{hist, Validation, Violation}
import org.typelevel.ci.CIString

final case class Output[A](results: Output.Results[A], errors: Output.Results[Errors]) extends Structure[A] {
  override type Self[a] = Output[a]

  def modifyResults[T](f: Output.Results[A] => Output.Results[T]): Output[T] = copy(results = f(results))
  def modifyErrors(f: Output.Results[Errors] => Output.Results[Errors]): Output[A] = copy(errors = f(errors))

  override private[structure] def vimap[T](
      f: A => Validated[Errors, T],
      g: T => A,
      validation: Option[Validation[_, _]]
  ): Output[T] = copy(results.vimap(f, g, validation))

  def fromResponse(response: Response): Validated[Errors, A] =
    errors.fromResponse(response) match {
      case Validated.Valid(Some(errors)) => errors.invalid
      case Validated.Valid(None) | Validated.Invalid(_) =>
        results.fromResponse(response).andThen {
          case Some(a) => a.valid
          case None    => Errors.oneNel(hist / "code", Violation.invalid(actual = response.code.value)).invalid
        }
    }
  def toResponse(a: Validated[Errors, A]): Response = a.fold(errors.toResponse, results.toResponse)
}

object Output {
  sealed abstract class Results[A] extends Structure.Sum[A] { self =>
    final override type Self[a] = Results[a]
    final override type Element[a] = Output.Result[a]
    final override type Group[a] = Results[a]

    def toChain: Chain[Output.Result[_]]

    override private[structure] def vimap[T](
        f: A => Validated[Errors, T],
        g: T => A,
        validation: Option[Validation[_, _]]
    ): Output.Results[T] = new Results[T] {
      override def toChain: Chain[Result[_]] = self.toChain
      override def fromResponse(response: Response): Validated[Errors, Option[T]] =
        self.fromResponse(response).andThen(_.traverse(f))
      override def toResponse(t: T): Response = self.toResponse(g(t))
    }

    final override def orElseAll[T](results: Output.Results[T]): Output.Results[A |+| T] = new Results[A |+| T] {
      override def toChain: Chain[Result[_]] = self.toChain ++ results.toChain
      override def fromResponse(response: Response): Validated[Errors, Option[A |+| T]] =
        self.fromResponse(response).andThen {
          case Some(a) => a.asLeft.some.valid
          case None    => results.fromResponse(response).map(_.map(_.asRight))
        }
      override def toResponse(at: A |+| T): Response = at match {
        case Left(a)  => self.toResponse(a)
        case Right(t) => results.toResponse(t)
      }
    }
    override def orElse[T](result: Output.Result[T]): Output.Results[A |+| T] = orElseAll(Results.fromResult(result))

    def fromResponse(response: Response): Validated[Errors, Option[A]]
    def toResponse(a: A): Response
  }

  object Results {
    def fromResult[A](result: Output.Result[A]): Output.Results[A] = new Results[A] {
      override def toChain: Chain[Result[_]] = Chain.one(result)
      override def fromResponse(response: Response): Validated[Errors, Option[A]] =
        if (result.code === response.code) result.fromResponse(response).map(_.some) else none[A].valid
      override def toResponse(a: A): Response = result.toResponse(a)
    }
  }

  sealed abstract class Result[A](val code: Code, val headers: Chain[(CIString, String)]) extends Structure.Sum[A] {
    self =>
    final override type Self[a] = Output.Result[a]
    final override type Element[a] = Output.Result[a]
    final override type Group[a] = Output.Results[a]

    override private[structure] def vimap[T](
        f: A => Validated[Errors, T],
        g: T => A,
        validation: Option[Validation[_, _]]
    ): Output.Result[T] = new Result[T](code, headers) {
      override def fromResponse(response: Response): Validated[Errors, T] = self.fromResponse(response).andThen(f)
      override def toResponse(t: T): Response = self.toResponse(g(t))
    }

    def toResults: Output.Results[A] = Output.Results.fromResult(this)
    final override def orElseAll[T](results: Results[T]): Results[A |+| T] = toResults orElseAll results
    final override def orElse[T](result: Result[T]): Output.Results[A |+| T] = toResults orElse result

    def fromResponse(response: Response): Validated[Errors, A]
    def toResponse(a: A): Response
  }

  object Result {
    def fromSchema[A](code: Code, headers: Chain[(CIString, String)], schema: Schema[A]): Output.Result[A] =
      new Result[A](code, headers) {
        override def fromResponse(response: Response): Validated[Errors, A] = schema.fromJson(response.body)
        override def toResponse(a: A): Response = Response(this.code, this.headers, schema.toJson(a))
      }

    def empty(code: Code, headers: Chain[(CIString, String)]): Output.Result[Unit] =
      new Result[Unit](code, headers) {
        override def fromResponse(response: Response): Validated[Errors, Unit] = ().valid
        override def toResponse(a: Unit): Response = Response(this.code, this.headers, none)
      }

    implicit val invariant: Invariant[Output.Result] = new Invariant[Result] {
      override def imap[A, B](fa: Result[A])(f: A => B)(g: B => A): Result[B] = fa.imap(f)(g)
    }
  }

  implicit val invariant: Invariant[Output] = new Invariant[Output] {
    override def imap[A, B](fa: Output[A])(f: A => B)(g: B => A): Output[B] = fa.imap(f)(g)
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy