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

ciris.ConfigValue.scala Maven / Gradle / Ivy

There is a newer version: 0.12.1
Show newest version
package ciris

import ciris.api._
import ciris.api.syntax._

/**
  * [[ConfigValue]] represents the value part of a [[ConfigEntry]],
  * without any other details like the key, key type, or original
  * source value. [[ConfigEntry]] extends [[ConfigValue]], but
  * you can also create one with [[ConfigValue#apply]].
  *
  * @tparam F the context in which the value exists
  * @tparam V the type of the value
  */
abstract class ConfigValue[F[_]: Apply, V] extends ConfigResult[F, V] {
  def value: F[Either[ConfigError, V]]

  /**
    * Returns the result of having loaded a configuration using
    * this [[ConfigValue]]. This effectively only wraps errors
    * from [[value]] in [[ConfigErrors]].
    *
    * @return the configuration loading result using this value
    */
  final override def result: F[Either[ConfigErrors, V]] =
    value.map(_.left.map(ConfigErrors(_)))

  /**
    * If the key for this value was missing from the [[ConfigSource]],
    * as determined by [[ConfigError.isMissingKey]], then uses `that`
    * [[ConfigValue]] instead. If `that` value is not available due
    * to an error, then the errors are accumulated.
    *
    * {{{
    * scala> val apiKey =
    *      |  env[String]("API_KEY").
    *      |    orElse(prop("api.key"))
    * apiKey: ConfigValue[api.Id, String] = ConfigValue$$1815631407
    *
    * scala> apiKey.value.left.map(_.message).toString
    * res0: String = Left(Missing environment variable [API_KEY] and missing system property [api.key])
    *
    * scala> env[String]("FILE_ENCODING").
    *      |   orElse(prop("file.encoding")).
    *      |   toStringWithValue
    * res1: String = ConfigValue(Right(UTF8))
    * }}}
    *
    * If the value is unavailable due to a different error than the
    * key missing from the [[ConfigSource]], then the alternative
    * value will not be used.
    *
    * {{{
    * scala> prop[Int]("file.encoding").
    *      |   orElse(env("FILE_ENCODING")).
    *      |   toStringWithValue
    * res2: String = ConfigValue(Left(WrongType(file.encoding, Property, Right(UTF8), UTF8, Int, java.lang.NumberFormatException: For input string: "UTF8")))
    * }}}
    *
    * Note that the alternative value is passed by reference, and it
    * will only be evaluated if this [[ConfigValue]] is unavailable
    * due to the key missing from the [[ConfigSource]].
    *
    * @param that the [[ConfigValue]] to use if this value is unavailable
    *             due to the key missing from the [[ConfigSource]]
    * @return a new [[ConfigValue]]
    */
  final def orElse(that: => ConfigValue[F, V])(implicit m: Monad[F]): ConfigValue[F, V] =
    ConfigValue.applyF[F, V] {
      this.value.flatMap {
        case Left(thisError) if thisError.isMissingKey =>
          that.value.map {
            case right @ Right(_) => right
            case Left(thatError)  => Left(thisError combine thatError)
          }

        case other => other.pure[F]
      }
    }

  /**
    * If the key for this value was missing from the [[ConfigSource]],
    * as determined by [[ConfigError.isMissingKey]], then uses `None`
    * as value instead. If the value is available, it gets wrapped
    * in a `Some`.
    *
    * {{{
    * scala> env[String]("API_KEY").
    *      |   orElse(prop("api.key")).
    *      |   orNone.
    *      |   toStringWithValue
    * res0: String = ConfigValue(Right(None))
    * }}}
    *
    * If the value is unavailable due to a different error than the
    * key missing from the [[ConfigSource]], then the error will
    * not be replaced with `None`.
    *
    * {{{
    * scala> prop[Int]("file.encoding").orNone.toStringWithValue
    * res1: String = ConfigValue(Left(WrongType(file.encoding, Property, Right(UTF8), UTF8, Int, java.lang.NumberFormatException: For input string: "UTF8")))
    * }}}
    *
    * @return a new [[ConfigValue]]
    */
  final def orNone: ConfigValue[F, Option[V]] =
    ConfigValue.applyF[F, Option[V]] {
      this.value.map {
        case Left(error) if error.isMissingKey => Right(None)
        case other                             => other.right.map(Some.apply)
      }
    }

  override def toString: String =
    "ConfigValue$" + System.identityHashCode(this)

  /**
    * Returns a `String` representation of this [[ConfigValue]]
    * including the value. If the value is potentially sensitive,
    * then be careful to not include it in log output.
    *
    * @return a `String` representation with the value
    */
  def toStringWithValue: String =
    s"ConfigValue($value)"

  override def toStringWithResult: String =
    s"ConfigValue($result)"
}

object ConfigValue {

  /**
    * Creates a new [[ConfigValue]] from the specified value.
    *
    * @param value the value or an error
    * @tparam V the type of the value
    * @return a new [[ConfigValue]]
    */
  def apply[V](value: Either[ConfigError, V]): ConfigValue[Id, V] =
    ConfigValue.applyF[Id, V](value)

  /**
    * Creates a new [[ConfigValue]] from the specified value,
    * wrapped in context `F`, which can be [[api.Id]] if no
    * context is desired. [[ConfigValue#apply]] also exists
    * for the case when `F` is [[api.Id]].
    *
    * @param value the value or an error, in context `F`
    * @tparam F the context in which the value exists
    * @tparam V the type of the value
    * @return a new [[ConfigValue]]
    */
  def applyF[F[_]: Apply, V](value: F[Either[ConfigError, V]]): ConfigValue[F, V] = {
    val theValue = value
    new ConfigValue[F, V] {
      override def value: F[Either[ConfigError, V]] = theValue
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy