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

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

package zio.config

import com.github.ghik.silencer.silent
import zio.config.ReadError._
import zio.{IO, ZIO, ZManaged}

import scala.collection.mutable.{Map => MutableMap}

import PropertyTreePath.Step

@silent("Unused import")
private[config] trait ReadModule extends ConfigDescriptorModule {
  import VersionSpecificSupport._

  type CachedReaders = MutableMap[ConfigSource, ConfigSource.ManagedReader]

  final def read[A](
    configuration: ConfigDescriptor[A]
  ): IO[ReadError[K], A] = {
    type Res[+B] = ZManaged[Any, ReadError[K], AnnotatedRead[PropertyTree[K, B]]]

    val cachedSources: CachedReaders = MutableMap()

    import ConfigDescriptorAdt._

    def formatError(paths: List[Step[K]], actual: String, expected: String, descriptions: List[String]) =
      ReadError.FormatError(
        paths.reverse,
        s"Provided value is $actual, expecting the type $expected",
        descriptions
      )

    def loopNested[B](
      path: List[Step[K]],
      cfg: Nested[B],
      descriptions: List[String],
      programSummary: List[ConfigDescriptor[_]]
    ): Res[B] =
      loopAny(path, cfg.config, descriptions, programSummary)

    def loopOptional[B](
      path: List[Step[K]],
      cfg: Optional[B],
      descriptions: List[String],
      programSummary: List[ConfigDescriptor[_]]
    ): Res[Option[B]] =
      loopAny(path, cfg.config, descriptions, programSummary).either flatMap {
        case Left(error) =>
          ZManaged.fromEither(handleDefaultValues(error, cfg.config, None))

        case Right(value) =>
          ZManaged.succeed(
            AnnotatedRead(value.value.map(Some(_)), Set(AnnotatedRead.Annotation.NonDefaultValue) ++ value.annotations)
          )
      }

    def loopDefault[B](
      path: List[Step[K]],
      cfg: Default[B],
      descriptions: List[String],
      programSummary: List[ConfigDescriptor[_]]
    ): Res[B] =
      loopAny(path, cfg.config, descriptions, programSummary).either flatMap {
        case Left(error) =>
          ZManaged.fromEither(handleDefaultValues(error, cfg.config, cfg.default))

        case Right(value) =>
          ZManaged.succeed(
            AnnotatedRead(value.value, Set(AnnotatedRead.Annotation.NonDefaultValue) ++ value.annotations)
          )
      }

    def loopOrElse[B](
      path: List[Step[K]],
      cfg: OrElse[B],
      descriptions: List[String],
      programSummary: List[ConfigDescriptor[_]]
    ): Res[B] =
      loopAny(path, cfg.left, descriptions, programSummary).either flatMap {
        case a @ Right(_) =>
          ZManaged.fromEither(a)

        case Left(leftError) =>
          loopAny(path, cfg.right, descriptions, programSummary).either flatMap {
            case a @ Right(_) =>
              ZManaged.fromEither(a)

            case Left(rightError) =>
              ZManaged.fail(
                ReadError.OrErrors(leftError :: rightError :: Nil, leftError.annotations ++ rightError.annotations)
              )
          }
      }

    def loopOrElseEither[B, C](
      path: List[Step[K]],
      cfg: OrElseEither[B, C],
      descriptions: List[String],
      programSummary: List[ConfigDescriptor[_]]
    ): Res[Either[B, C]] =
      loopAny(path, cfg.left, descriptions, programSummary).either flatMap {
        case Right(value) =>
          ZManaged.succeed(value.map(_.map(Left(_))))

        case Left(leftError) =>
          loopAny(path, cfg.right, descriptions, programSummary).either flatMap {
            case Right(rightValue) =>
              ZManaged.succeed(rightValue.map(_.map(Right(_))))

            case Left(rightError) =>
              ZManaged.fail(
                ReadError.OrErrors(leftError :: rightError :: Nil, leftError.annotations ++ rightError.annotations)
              )
          }
      }

    def loopSource[B](
      path: List[Step[K]],
      cfg: Source[B],
      descriptions: List[String]
    ): Res[B] =
      for {
        maybeMemoizedReader <- cachedSources.get(cfg.source) match {
                                 case Some(value) =>
                                   ZManaged.succeed(value)
                                 case None        =>
                                   cfg.source.run.access
                               }
        _                   <- ZManaged.succeed(cachedSources.update(cfg.source, maybeMemoizedReader))
        tree                <- ZManaged.fromZIO(maybeMemoizedReader.use(_(PropertyTreePath(path.reverse.toVector))))
        res                 <- tree match {
                                 case PropertyTree.Empty          =>
                                   ZManaged.fail(ReadError.MissingValue(path.reverse, descriptions))
                                 case PropertyTree.Record(_)      =>
                                   ZManaged.fail(formatError(path, "of type Map", "Singleton", descriptions))
                                 case PropertyTree.Sequence(_)    =>
                                   ZManaged.fail(formatError(path, "of type List", "Singleton", descriptions))
                                 case PropertyTree.Leaf(value, _) =>
                                   cfg.propertyType.read(value) match {
                                     case Left(parseError) =>
                                       ZManaged.fail(
                                         formatError(
                                           path,
                                           parseError.value,
                                           parseError.typeInfo,
                                           descriptions
                                         )
                                       )
                                     case Right(parsed)    =>
                                       ZManaged.succeed(AnnotatedRead(PropertyTree.Leaf(parsed), Set.empty))
                                   }
                               }
      } yield res

    def loopZip[B, C](
      path: List[Step[K]],
      cfg: Zip[B, C],
      descriptions: List[String],
      programSummary: List[ConfigDescriptor[_]]
    ): Res[(B, C)] =
      loopAny(path, cfg.left, descriptions, programSummary).either
        .zip(loopAny(path, cfg.right, descriptions, programSummary).either)
        .flatMap {
          case (Right(leftV), Right(rightV)) =>
            ZManaged.succeed(leftV.zipWith(rightV)(_.zip(_)))

          case (Left(error1), Left(error2)) =>
            ZManaged.fail(ZipErrors(error1 :: error2 :: Nil, error1.annotations ++ error2.annotations))

          case (Left(error), Right(annotated)) =>
            ZManaged.fail(ZipErrors(error :: Nil, error.annotations ++ annotated.annotations))

          case (Right(annotated), Left(error)) =>
            ZManaged.fail(ZipErrors(error :: Nil, error.annotations ++ annotated.annotations))
        }

    def loopXmapEither[B, C](
      path: List[Step[K]],
      cfg: TransformOrFail[B, C],
      descriptions: List[String],
      programSummary: List[ConfigDescriptor[_]]
    ): Res[C] =
      loopAny(path, cfg.config, descriptions, programSummary).either flatMap {
        case Left(error) =>
          ZManaged.fail(error)
        case Right(a)    =>
          ZManaged.fromEither(
            a.mapEither(tree => tree.mapEither(cfg.f))
              .swap
              .map(message => ReadError.ConversionError(path.reverse, message, a.annotations))
              .swap
          )
      }

    def loopMap[B](
      path: List[Step[K]],
      cfg: DynamicMap[B],
      descriptions: List[String],
      programSummary: List[ConfigDescriptor[_]]
    ): Res[Map[K, B]] =
      for {
        tree_ <- treeOf(cfg, cachedSources)
        res   <- tree_.at(PropertyTreePath(path.reverse.toVector)) match {
                   case PropertyTree.Leaf(_, _)     =>
                     ZManaged.fail(formatError(path, "of type Singleton", "Map", descriptions))
                   case PropertyTree.Sequence(_)    =>
                     ZManaged.fail(formatError(path, "of type List", "Map", descriptions))
                   case PropertyTree.Record(values) =>
                     val result: List[(K, Res[B])] =
                       values.toList.map { case ((k, _)) =>
                         (
                           k,
                           loopAny(
                             Step.Key(k) :: path,
                             cfg.config,
                             descriptions,
                             programSummary
                           )
                         )
                       }

                     seqMap2[K, ReadError[K], B](result.map({ case (a, b) => (a, b.map(_.value)) }).toMap)
                       .mapError(errs => ReadError.MapErrors(errs, errs.flatMap(_.annotations).toSet))
                       .map(mapp => AnnotatedRead(mapp, Set.empty))

                   case PropertyTree.Empty => ZManaged.fail(ReadError.MissingValue(path.reverse, descriptions))
                 }
      } yield res

    def loopSequence[B](
      path: List[Step[K]],
      cfg: Sequence[B],
      descriptions: List[String],
      programSummary: List[ConfigDescriptor[_]]
    ): Res[List[B]] = {

      def fromTrees(
        values: List[PropertyTree[K, V]]
      ): ZManaged[Any, ReadError[String], AnnotatedRead[PropertyTree[K, List[B]]]] = {
        val list = values.zipWithIndex.map { case (_, idx) =>
          loopAny(
            Step.Index(idx) :: path,
            cfg.config,
            descriptions,
            programSummary
          )
        }

        seqEither2[K, ReadError[K], B, ReadError[K]]((_, a) => a)(list.map(res => res.map(_.value)))
          .mapError(errs => ReadError.ListErrors(errs, errs.flatMap(_.annotations).toSet))
          .map(list => AnnotatedRead(list, Set.empty))
      }

      for {
        tree_ <- treeOf(cfg, cachedSources)
        res   <- tree_.at(PropertyTreePath(path.reverse.toVector)) match {
                   case leaf @ PropertyTree.Leaf(_, _) => fromTrees(List(leaf))
                   case PropertyTree.Record(_)         => ZManaged.fail(formatError(path, "of type Map", "List", descriptions))
                   case PropertyTree.Empty             => ZManaged.fail(ReadError.MissingValue(path.reverse, descriptions))
                   case PropertyTree.Sequence(values)  => fromTrees(values)
                 }
      } yield res
    }

    def loopAny[B](
      path: List[Step[K]],
      config: ConfigDescriptor[B],
      descriptions: List[String],
      programSummary: List[ConfigDescriptor[_]]
    ): Res[B] =
      for {
        alreadySeen         <- ZManaged.succeed(programSummary.contains(config))
        isEmptyConfigSource <-
          if (alreadySeen) isEmptyConfigSource(config, path.reverse, cachedSources)
          else ZManaged.succeed(false)
        res                 <- if (isEmptyConfigSource) {
                                 ZManaged.fail(ReadError.MissingValue(path.reverse, descriptions))
                               } else
                                 config match {
                                   case c @ Lazy(thunk) =>
                                     loopAny(path, thunk(), descriptions, c :: programSummary)

                                   case c @ Default(_, _) =>
                                     loopDefault(path, c, descriptions, c :: programSummary)

                                   case c @ Describe(_, message) =>
                                     loopAny(path, c.config, descriptions :+ message, c :: programSummary)

                                   case c @ DynamicMap(_) =>
                                     loopMap(path, c, descriptions, c :: programSummary)

                                   case c @ Nested(key, _) =>
                                     loopNested(Step.Key(key) :: path, c, descriptions, c :: programSummary)

                                   case c @ Optional(_) =>
                                     loopOptional(path, c, descriptions, c :: programSummary)

                                   case c @ OrElse(_, _) =>
                                     loopOrElse(path, c, descriptions, c :: programSummary)

                                   case c @ OrElseEither(_, _) =>
                                     loopOrElseEither(path, c, descriptions, c :: programSummary)

                                   case c @ Source(_, _) =>
                                     loopSource(
                                       path,
                                       c,
                                       descriptions
                                     )

                                   case c @ Zip(_, _) =>
                                     loopZip(path, c, descriptions, c :: programSummary)

                                   case c @ TransformOrFail(_, _, _) =>
                                     loopXmapEither(path, c, descriptions, c :: programSummary)

                                   case c @ Sequence(_) =>
                                     loopSequence(path, c, descriptions, c :: programSummary)
                                 }

      } yield res.asInstanceOf[AnnotatedRead[PropertyTree[K, B]]]

    loopAny(Nil, configuration, Nil, Nil)
      .map(_.value)
      .use {
        case PropertyTree.Leaf(value, _) => ZIO.succeed(value)
        case _                           => ZIO.fail(ReadError.SourceError("Failed to read"))
      }

  }

  private[config] def treeOf[A](
    config: ConfigDescriptor[A],
    cachedSources: CachedReaders
  ): ZManaged[Any, ReadError[String], PropertyTree[K, V]] = {
    val sourceTrees = ZManaged.foreach(config.sources.toList) { managed =>
      for {
        existing      <- ZManaged.succeed(cachedSources.get(managed))
        managedReader <- existing match {
                           case Some(value) =>
                             ZManaged.succeed(value)
                           case None        =>
                             managed.run.access
                         }
        rootTree      <- ZManaged.fromZIO(managedReader.use(reader => reader(PropertyTreePath(Vector.empty))))
      } yield rootTree
    }

    sourceTrees.map(_.reduceLeftOption(_.getOrElse(_)).getOrElse(PropertyTree.empty))
  }

  private[config] def isEmptyConfigSource[A](
    config: ConfigDescriptor[A],
    keys: List[Step[K]],
    cachedSources: CachedReaders
  ): ZManaged[Any, ReadError[K], Boolean] =
    ZManaged.forall(config.sources) { managed =>
      for {
        existing      <- ZManaged.succeed(cachedSources.get(managed))
        managedReader <- existing match {
                           case Some(value) =>
                             ZManaged.succeed(value)
                           case None        =>
                             managed.run.access
                         }
        tree          <- ZManaged.fromZIO(managedReader.use(_(PropertyTreePath(keys.toVector))))
      } yield tree == PropertyTree.empty
    }

  private[config] def foldReadError[B](
    error: ReadError[K]
  )(alternative: B)(f: PartialFunction[ReadError[K], B])(g: (B, B) => B, zero: B): B = {
    def go(list: List[ReadError[K]]): B =
      list.foldLeft(zero)((a, b) => g(foldReadError(b)(alternative)(f)(g, zero), a))

    error match {
      case e @ ReadError.MissingValue(_, _, _)    => f.applyOrElse(e, (_: ReadError[K]) => alternative)
      case e @ ReadError.SourceError(_, _)        => f.applyOrElse(e, (_: ReadError[K]) => alternative)
      case e @ ReadError.FormatError(_, _, _, _)  => f.applyOrElse(e, (_: ReadError[K]) => alternative)
      case e @ ReadError.ConversionError(_, _, _) => f.applyOrElse(e, (_: ReadError[K]) => alternative)
      case e @ ReadError.Irrecoverable(list, _)   => f.applyOrElse(e, (_: ReadError[K]) => go(list))
      case e @ ReadError.OrErrors(list, _)        => f.applyOrElse(e, (_: ReadError[K]) => go(list))
      case e @ ReadError.ZipErrors(list, _)       => f.applyOrElse(e, (_: ReadError[K]) => go(list))
      case e @ ReadError.ListErrors(list, _)      => f.applyOrElse(e, (_: ReadError[K]) => go(list))
      case e @ ReadError.MapErrors(list, _)       => f.applyOrElse(e, (_: ReadError[K]) => go(list))
    }
  }

  private[config] def handleDefaultValues[A, B](
    error: ReadError[K],
    config: ConfigDescriptor[A],
    default: B
  ): Either[ReadError[K], AnnotatedRead[PropertyTree[K, B]]] = {

    val hasOnlyMissingValuesAndZeroIrrecoverable =
      foldReadError(error)(alternative = false) {
        case ReadError.MissingValue(_, _, _) => true
        case ReadError.Irrecoverable(_, _)   => false
      }(_ && _, true)

    val baseConditionForFallBack =
      hasOnlyMissingValuesAndZeroIrrecoverable && sizeOfZipAndOrErrors(error) == requiredZipAndOrFields(config)

    def hasZeroNonDefaultValues(annotations: Set[AnnotatedRead.Annotation]) =
      !annotations.contains(AnnotatedRead.Annotation.NonDefaultValue)

    error match {
      case MissingValue(_, _, annotations) => Right(AnnotatedRead(PropertyTree.Leaf(default), annotations))

      case ReadError.ZipErrors(_, annotations) if baseConditionForFallBack && hasZeroNonDefaultValues(annotations) =>
        Right(AnnotatedRead(PropertyTree.Leaf(default), annotations))

      case ReadError.OrErrors(_, annotations) if baseConditionForFallBack && hasZeroNonDefaultValues(annotations) =>
        Right(AnnotatedRead(PropertyTree.Leaf(default), annotations))

      case e =>
        Left(Irrecoverable(List(e)))
    }
  }

  private[config] def parseErrorMessage(givenValue: String, expectedType: String) =
    s"Provided value is ${givenValue.toString}, expecting the type ${expectedType}"

  final def requiredZipAndOrFields[A](config: ConfigDescriptor[A]): Int = {
    def loop[B](count: List[K], config: ConfigDescriptor[B]): Int =
      config match {
        case ConfigDescriptorAdt.Lazy(thunk)                => loop(count, thunk())
        case ConfigDescriptorAdt.Zip(left, right)           => loop(count, left) + loop(count, right)
        case ConfigDescriptorAdt.TransformOrFail(cfg, _, _) => loop(count, cfg)
        case ConfigDescriptorAdt.Describe(cfg, _)           => loop(count, cfg)
        case ConfigDescriptorAdt.Nested(_, next)            => loop(count, next)
        case ConfigDescriptorAdt.Source(_, _)               => 1
        case ConfigDescriptorAdt.Optional(_)                => 0
        case ConfigDescriptorAdt.OrElse(left, right)        => loop(count, left) + loop(count, right)
        case ConfigDescriptorAdt.OrElseEither(left, right)  => loop(count, left) + loop(count, right)
        case ConfigDescriptorAdt.Default(_, _)              => 0
        case ConfigDescriptorAdt.Sequence(_)                => 1
        case ConfigDescriptorAdt.DynamicMap(_)              => 1
      }

    loop(Nil, config)
  }

  def sizeOfZipAndOrErrors(error: ReadError[K]): Int =
    foldReadError(error)(0) {
      case ReadError.ListErrors(_, _)         => 1
      case ReadError.MapErrors(_, _)          => 1
      case ReadError.Irrecoverable(_, _)      => 1
      case ReadError.MissingValue(_, _, _)    => 1
      case ReadError.FormatError(_, _, _, _)  => 1
      case ReadError.ConversionError(_, _, _) => 1
    }(_ + _, 0)
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy