![JAR search and dependency download from the Maven repository](/logo.png)
pureconfig.generic.CoproductHint.scala Maven / Gradle / Ivy
package pureconfig.generic
import com.typesafe.config.{ConfigObject, ConfigValue, ConfigValueType}
import pureconfig._
import pureconfig.error._
import pureconfig.generic.CoproductHint.{Attempt, Use}
import pureconfig.generic.error.{
CoproductHintException,
NoValidCoproductOptionFound,
UnexpectedValueForFieldCoproductHint
}
import pureconfig.syntax._
/** A trait that can be implemented to disambiguate between the different options of a coproduct or sealed family.
*
* @tparam A
* the type of the coproduct or sealed family for which this hint applies
*/
trait CoproductHint[A] {
/** Given a `ConfigCursor` for the sealed family, disambiguate and return what should be performed when trying to read
* one of the provided coproduct options. This method can decide either to:
* - use the `ConfigCursor` with a single option ([[CoproductHint.Use]]);
* - or attempt different options in a given order ([[CoproductHint.Attempt]]).
*
* This method can return a `Left` if the hint fails to produce a valid [[CoproductHint.Action]].
*
* @param cursor
* a `ConfigCursor` at the sealed family option
* @param options
* the names of the coproduct options for the given type
* @return
* a `ConfigReader.Result` of [[CoproductHint.Action]] as defined above.
*/
def from(cursor: ConfigCursor, options: Seq[String]): ConfigReader.Result[CoproductHint.Action]
/** Given the `ConfigValue` for a specific class or coproduct option, encode disambiguation information and return a
* config for the sealed family or coproduct.
*
* @param value
* the `ConfigValue` of the class or coproduct option
* @param name
* the name of the class or coproduct option
* @return
* the config for the sealed family or coproduct wrapped in a `Right`, or a `Left` with the failure if some error
* occurred.
*/
def to(value: ConfigValue, name: String): ConfigValue
}
/** Hint where the options are disambiguated by a `key = "value"` field inside the config.
*
* This hint will cause derived `ConfigConvert` instance to fail to convert configs to objects if the object has a
* field with the same name as the disambiguation key.
*
* By default, the field value written is the class or coproduct option name converted to kebab case. This mapping can
* be changed by overriding the method `fieldValue` of this class.
*/
class FieldCoproductHint[A](key: String) extends CoproductHint[A] {
/** Returns the field value for a class or coproduct option name.
*
* @param name
* the name of the class or coproduct option
* @return
* the field value associated with the given class or coproduct option name.
*/
protected def fieldValue(name: String): String = FieldCoproductHint.defaultMapping(name)
def from(cursor: ConfigCursor, options: Seq[String]): ConfigReader.Result[CoproductHint.Action] = {
for {
objCur <- cursor.asObjectCursor
valueCur <- objCur.atKey(key)
valueStr <- valueCur.asString
option <-
options
.find(valueStr == fieldValue(_))
.toRight(
ConfigReaderFailures(valueCur.failureFor(UnexpectedValueForFieldCoproductHint(valueCur.valueOpt.get)))
)
} yield Use(objCur.withoutKey(key), option)
}
def to(value: ConfigValue, name: String): ConfigValue = {
value match {
case co: ConfigObject if co.containsKey(key) =>
throw CoproductHintException(CollidingKeys(key, co.get(key)))
case co: ConfigObject =>
Map(key -> fieldValue(name)).toConfig.withFallback(co.toConfig)
case _ =>
throw CoproductHintException(WrongType(value.valueType, Set(ConfigValueType.OBJECT)))
}
}
}
object FieldCoproductHint {
val defaultMapping: String => String = ConfigFieldMapping(PascalCase, KebabCase)
}
/** Hint where all coproduct options are tried in order. `from` will choose the first option able to deserialize the
* config without errors, while `to` will write the config as is, with no disambiguation information.
*/
class FirstSuccessCoproductHint[A] extends CoproductHint[A] {
def from(cursor: ConfigCursor, options: Seq[String]): ConfigReader.Result[CoproductHint.Action] =
Right(
Attempt(
cursor,
options,
failures =>
cursor.asConfigValue
.fold(identity, v => ConfigReaderFailures(cursor.failureFor(NoValidCoproductOptionFound(v, failures))))
)
)
def to(value: ConfigValue, name: String): ConfigValue =
value
}
object CoproductHint {
/** What should be done when reading a given coproduct option.
*/
sealed trait Action {
/** The `ConfigCursor` to use when trying to read the coproduct option.
*/
def cursor: ConfigCursor
}
/** An action to only use the provided `ConfigCursor` and not try other options.
*
* @param cursor
* the `ConfigCursor` to use when reading the coproduct option
* @param option
* the coproduct option to consider when reading from the provider cursor
*/
case class Use(cursor: ConfigCursor, option: String) extends Action
/** An action to attempt to use the provided coproduct options, in the specified order, stopping at the first one that
* reads successfully.
*
* @param cursor
* the `ConfigCursor` to use when reading the coproduct option
* @param options
* the coproduct options to attempt reading, in order
* @param combineFailures
* the function to combine all failures in case all attempts to read fail
*/
case class Attempt(
cursor: ConfigCursor,
options: Seq[String],
combineFailures: Seq[(String, ConfigReaderFailures)] => ConfigReaderFailures
) extends Action
implicit def default[A]: CoproductHint[A] = new FieldCoproductHint[A]("type")
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy