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

zio.config.ReadError.scala Maven / Gradle / Ivy

package zio.config

import zio.config.AnnotatedRead.Annotation

import scala.util.control.NoStackTrace

import PropertyTreePath._

sealed trait ReadError[+A] extends Exception with NoStackTrace { self =>
  def annotations: Set[Annotation]

  override def getMessage: String =
    prettyPrint()

  final def nonPrettyPrintedString: String = self match {
    case ReadError.MissingValue(path, message, annotations)        =>
      s"MissingValue($path, $message, $annotations)"
    case ReadError.SourceError(message, annotations)               =>
      s"SourceError($message, $annotations)"
    case ReadError.FormatError(path, message, detail, annotations) =>
      s"FormatError($path, $message, $detail, $annotations)"
    case ReadError.ConversionError(path, message, annotations)     =>
      s"ConversionError($path, $message, $annotations"
    case ReadError.OrErrors(list, annotations)                     =>
      s"OrErrors(${list.map(_.nonPrettyPrintedString)}, $annotations)"
    case ReadError.ZipErrors(list, annotations)                    =>
      s"ZipErrors(${list.map(_.nonPrettyPrintedString)}, $annotations)"
    case ReadError.ListErrors(list, annotations)                   =>
      s"ListErrors(${list.map(_.nonPrettyPrintedString)}, $annotations)"
    case ReadError.MapErrors(list, annotations)                    =>
      s"MapErrors(${list.map(_.nonPrettyPrintedString)}, $annotations)"
    case ReadError.Irrecoverable(list, annotations)                =>
      s"Irrecoverable(${list.map(_.nonPrettyPrintedString)}, $annotations)"
  }

  final def prettyPrint(keyDelimiter: Char = '.'): String = {

    sealed trait Segment
    sealed trait Step extends Segment

    final case class Sequential(all: List[Step])     extends Segment
    final case class Parallel(all: List[Sequential]) extends Step
    final case class Failure(lines: List[String])    extends Step

    def renderSteps(steps: List[PropertyTreePath.Step[A]]): String =
      steps
        .foldLeft(new StringBuilder()) {
          case (r, Step.Key(k))   => r.append(keyDelimiter).append(k.toString)
          case (r, Step.Index(i)) => r.append('[').append(i).append(']')
        }
        .delete(0, 1)
        .toString()

    def prefixBlock(values: List[String], p1: String, p2: String): List[String] =
      values match {
        case Nil          => Nil
        case head :: tail =>
          (p1 + head) :: tail.map(p2 + _)
      }

    def parallelSegments(readError: ReadError[A]): List[Sequential] =
      readError match {
        case ReadError.ZipErrors(head :: tail, _)     => parallelSegments(head) ++ tail.flatMap(parallelSegments)
        case ReadError.ListErrors(head :: tail, _)    => parallelSegments(head) ++ tail.flatMap(parallelSegments)
        case ReadError.MapErrors(head :: tail, _)     => parallelSegments(head) ++ tail.flatMap(parallelSegments)
        case ReadError.Irrecoverable(head :: tail, _) => parallelSegments(head) ++ tail.flatMap(parallelSegments)
        case _                                        => List(readErrorToSequential(readError))
      }

    def linearSegments(readError: ReadError[A]): List[Step] =
      readError match {
        case ReadError.OrErrors(head :: tail, _) => linearSegments(head) ++ tail.flatMap(linearSegments)
        case _                                   => readErrorToSequential(readError).all
      }

    def renderMissingValue(err: ReadError.MissingValue[A]): Sequential = {
      val strings =
        "MissingValue" :: s"path: ${renderSteps(err.path)}" :: Nil

      Sequential(
        err.detail match {
          case ::(head, next) =>
            List(Failure(strings :+ s"Details: ${(head :: next).mkString(", ")}"))
          case Nil            =>
            List(Failure(strings))
        }
      )
    }

    def renderFormatError(err: ReadError.FormatError[A]): Sequential = {
      val strings =
        "FormatError" :: s"cause: ${err.message}" :: s"path: ${renderSteps(err.path)}" :: Nil

      Sequential(
        err.detail match {
          case ::(head, next) =>
            List(Failure(strings :+ s"Details: ${(head :: next).mkString(", ")}"))
          case Nil            =>
            List(Failure(strings))
        }
      )
    }

    def renderConversionError(err: ReadError.ConversionError[A]): Sequential =
      Sequential(
        List(
          Failure(
            "ConversionError" :: s"cause: ${err.message}" :: s"path: ${renderSteps(err.path)}" :: Nil
          )
        )
      )

    def renderSourceError(err: ReadError.SourceError): Sequential =
      Sequential(
        List(
          Failure(s"SourceError: ${err.message}" :: Nil)
        )
      )

    def readErrorToSequential(readError: ReadError[A]): Sequential =
      readError match {
        case r: ReadError.MissingValue[A]    => renderMissingValue(r)
        case r: ReadError.SourceError        => renderSourceError(r)
        case r: ReadError.FormatError[A]     => renderFormatError(r)
        case r: ReadError.ConversionError[A] => renderConversionError(r)
        case t: ReadError.OrErrors[A]        => Sequential(linearSegments(t))
        case b: ReadError.ZipErrors[A]       => Sequential(List(Parallel(parallelSegments(b))))
        case b: ReadError.ListErrors[A]      => Sequential(List(Parallel(parallelSegments(b))))
        case b: ReadError.MapErrors[A]       => Sequential(List(Parallel(parallelSegments(b))))
        case b: ReadError.Irrecoverable[A]   => Sequential(List(Parallel(parallelSegments(b))))
      }

    def format(segment: Segment): List[String] =
      segment match {
        case Failure(lines)  =>
          prefixBlock(lines, "─", " ")
        case Parallel(all)   =>
          List(("══╦" * (all.size - 1)) + "══╗") ++
            all.foldRight[List[String]](Nil) { case (current, acc) =>
              prefixBlock(acc, "  ║", "  ║") ++
                prefixBlock(format(current), "  ", "  ")
            }
        case Sequential(all) =>
          all.flatMap { segment =>
            List("║") ++
              prefixBlock(format(segment), "╠", "║")
          } ++ List("▼")
      }

    val sequence = readErrorToSequential(self)

    ("ReadError:" :: {
      sequence match {
        // use simple report for single failures
        case Sequential(List(Failure(readError))) => readError

        case _ => format(sequence).updated(0, "╥")
      }
    }).mkString(System.lineSeparator())
  }

  def size: Int =
    self match {
      case ReadError.MissingValue(_, _, _)    => 1
      case ReadError.SourceError(_, _)        => 1
      case ReadError.FormatError(_, _, _, _)  => 1
      case ReadError.ConversionError(_, _, _) => 1
      case ReadError.OrErrors(list, _)        => list.map(_.size).sum
      case ReadError.ZipErrors(list, _)       => list.map(_.size).sum
      case ReadError.ListErrors(list, _)      => list.map(_.size).sum
      case ReadError.MapErrors(list, _)       => list.map(_.size).sum
      case ReadError.Irrecoverable(list, _)   => list.map(_.size).sum
    }

  override def toString: String =
    prettyPrint()
}

object ReadError {
  final case class MissingValue[A](
    path: List[PropertyTreePath.Step[A]],
    detail: List[String] = Nil,
    annotations: Set[Annotation] = Set.empty
  ) extends ReadError[A]

  final case class FormatError[A](
    path: List[PropertyTreePath.Step[A]],
    message: String,
    detail: List[String] = Nil,
    annotations: Set[Annotation] = Set.empty
  ) extends ReadError[A]

  final case class ConversionError[A](
    path: List[PropertyTreePath.Step[A]],
    message: String,
    annotations: Set[Annotation] = Set.empty
  ) extends ReadError[A]

  final case class Irrecoverable[A](list: List[ReadError[A]], annotations: Set[Annotation] = Set.empty)
      extends ReadError[A]

  final case class OrErrors[A](list: List[ReadError[A]], annotations: Set[Annotation] = Set.empty) extends ReadError[A]

  final case class ZipErrors[A](list: List[ReadError[A]], annotations: Set[Annotation] = Set.empty) extends ReadError[A]

  final case class ListErrors[A](list: List[ReadError[A]], annotations: Set[Annotation] = Set.empty)
      extends ReadError[A]

  final case class MapErrors[A](list: List[ReadError[A]], annotations: Set[Annotation] = Set.empty) extends ReadError[A]

  final case class SourceError(message: String, annotations: Set[Annotation] = Set.empty) extends ReadError[Nothing]
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy