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

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

The newest version!
package io.hireproof.structure

import cats.data.Validated
import cats.syntax.all._
import cats.{Eval, Invariant}
import io.circe.Json
import io.circe.syntax._
import io.hireproof.screening.{Constraint, Validation, Violation}

final case class Branch[A](name: String, schema: Eval[Schema[A]]) extends Structure.Sum[A] {
  override type Self[a] = Branch[a]
  override type Element[a] = Branch[a]
  override type Group[a] = Schema.Sum[a]

  override def orElseAll[T](schema: Schema.Sum[T]): Schema.Sum[A |+| T] = toSum.orElseAll(schema)
  override def orElse[T](branch: Branch[T]): Schema.Sum[A |+| T] = toSum.orElse(branch)

  def toSum: Schema.Sum[A] = Schema.Sum.fromBranch(this)

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

  def fromJson(discriminator: Option[Discriminator], json: Option[Json]): Validated[Errors, Option[A]] =
    discriminator match {
      case Some(Discriminator.Nested(identifier, value)) =>
        json
          .toValid(Errors.rootNel(Violation(Constraint.required, json)))
          .map(_.hcursor)
          .andThen { cursor =>
            cursor
              .get[Option[Json]](identifier)
              .toValidated
              .leftMap(Errors.fromThrowable)
              .andThen(_.toValid(Errors.rootNel(Violation(Constraint.required, Json.Null))))
              .andThen { json =>
                json
                  .as[String]
                  .toValidated
                  .leftMap(_ => Errors.rootNel(Violation(Constraint.json("string"), json)))
              }
              .leftMap(_.modifyHistory(identifier /: _))
              .andThen { identifier =>
                if (name === identifier)
                  cursor
                    .get[Option[Json]](value)
                    .toValidated
                    .leftMap(Errors.fromThrowable)
                    .andThen(schema.value.fromJson)
                    .leftMap(_.modifyHistory(value /: _))
                    .map(_.some)
                else none[A].valid
              }
          }
      case Some(Discriminator.Merged(identifier)) =>
        json
          .toValid(Errors.rootNel(Violation(Constraint.required, json)))
          .map(_.hcursor)
          .andThen { cursor =>
            cursor
              .get[Option[Json]](identifier)
              .toValidated
              .leftMap(Errors.fromThrowable)
              .andThen(_.toValid(Errors.rootNel(Violation(Constraint.required, Json.Null))))
              .andThen { json =>
                json
                  .as[String]
                  .toValidated
                  .leftMap(_ => Errors.rootNel(Violation(Constraint.json("string"), json)))
              }
              .leftMap(_.modifyHistory(identifier /: _))
              .andThen { identifier =>
                if (name === identifier) schema.value.fromJson(json).map(_.some) else none[A].valid
              }
          }
      case None => schema.value.fromJson(json).toOption.valid
    }

  def toJson(discriminator: Option[Discriminator], a: A): Option[Json] = discriminator match {
    case Some(Discriminator.Nested(identifier, value)) =>
      Json.obj(identifier := name, value := schema.value.toJson(a).getOrElse(Json.Null)).dropNullValues.some
    case Some(Discriminator.Merged(identifier)) =>
      (schema.value.toJson(a).getOrElse(Json.Null) deepMerge Json.obj(identifier := name)).some
    case None => schema.value.toJson(a)
  }
}

object Branch {
  def default[A](name: String, schema: => Schema[A]): Branch[A] = Branch(name, Eval.later(schema))

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




© 2015 - 2024 Weber Informatics LLC | Privacy Policy