• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pureconfig / pureconfig / 16844894943

09 Aug 2025 03:28AM UTC coverage: 94.686% (-0.04%) from 94.726%
16844894943

Pull #1838

web-flow
Merge 12f0062cf into 38f73c635
Pull Request #1838: Rid of deprecated URL constructor

1 of 1 new or added line in 1 file covered. (100.0%)

95 existing lines in 26 files now uncovered.

2744 of 2898 relevant lines covered (94.69%)

2.43 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.67
/modules/generic-base/src/main/scala/pureconfig/generic/CoproductHint.scala
1
package pureconfig.generic
2

3
import com.typesafe.config.{ConfigObject, ConfigValue, ConfigValueType}
4

5
import pureconfig._
6
import pureconfig.error._
7
import pureconfig.generic.CoproductHint.{Attempt, Use}
8
import pureconfig.generic.error.{
9
  CoproductHintException,
10
  NoValidCoproductOptionFound,
11
  UnexpectedValueForFieldCoproductHint
12
}
13
import pureconfig.syntax._
14

15
/** A trait that can be implemented to disambiguate between the different options of a coproduct or sealed family.
16
  *
17
  * @tparam A
18
  *   the type of the coproduct or sealed family for which this hint applies
19
  */
20
trait CoproductHint[A] {
21

22
  /** Given a `ConfigCursor` for the sealed family, disambiguate and return what should be performed when trying to read
23
    * one of the provided coproduct options. This method can decide either to:
24
    *   - use the `ConfigCursor` with a single option ([[CoproductHint.Use]]);
25
    *   - or attempt different options in a given order ([[CoproductHint.Attempt]]).
26
    *
27
    * This method can return a `Left` if the hint fails to produce a valid [[CoproductHint.Action]].
28
    *
29
    * @param cursor
30
    *   a `ConfigCursor` at the sealed family option
31
    * @param options
32
    *   the names of the coproduct options for the given type
33
    * @return
34
    *   a `ConfigReader.Result` of [[CoproductHint.Action]] as defined above.
35
    */
36
  def from(cursor: ConfigCursor, options: Seq[String]): ConfigReader.Result[CoproductHint.Action]
37

38
  /** Given the `ConfigValue` for a specific class or coproduct option, encode disambiguation information and return a
39
    * config for the sealed family or coproduct.
40
    *
41
    * @param value
42
    *   the `ConfigValue` of the class or coproduct option
43
    * @param name
44
    *   the name of the class or coproduct option
45
    * @return
46
    *   the config for the sealed family or coproduct wrapped in a `Right`, or a `Left` with the failure if some error
47
    *   occurred.
48
    */
49
  def to(value: ConfigValue, name: String): ConfigValue
50
}
51

52
/** Hint where the options are disambiguated by a `key = "value"` field inside the config.
53
  *
54
  * This hint will cause derived `ConfigConvert` instance to fail to convert configs to objects if the object has a
55
  * field with the same name as the disambiguation key.
56
  *
57
  * By default, the field value written is the class or coproduct option name converted to kebab case. This mapping can
58
  * be changed by overriding the method `fieldValue` of this class.
59
  */
60
class FieldCoproductHint[A](key: String) extends CoproductHint[A] {
61

62
  /** Returns the field value for a class or coproduct option name.
63
    *
64
    * @param name
65
    *   the name of the class or coproduct option
66
    * @return
67
    *   the field value associated with the given class or coproduct option name.
68
    */
69
  protected def fieldValue(name: String): String = FieldCoproductHint.defaultMapping(name)
3✔
70

71
  def from(cursor: ConfigCursor, options: Seq[String]): ConfigReader.Result[CoproductHint.Action] = {
1✔
72
    for {
1✔
73
      objCur <- cursor.asObjectCursor
3✔
74
      valueCur <- objCur.atKey(key)
3✔
75
      valueStr <- valueCur.asString
3✔
76
      option <-
3✔
77
        options
78
          .find(valueStr == fieldValue(_))
3✔
79
          .toRight(
1✔
80
            ConfigReaderFailures(valueCur.failureFor(UnexpectedValueForFieldCoproductHint(valueCur.valueOpt.get)))
3✔
81
          )
82
    } yield Use(objCur.withoutKey(key), option)
3✔
83
  }
84

85
  def to(value: ConfigValue, name: String): ConfigValue = {
1✔
86
    value match {
87
      case co: ConfigObject if co.containsKey(key) =>
3✔
88
        throw CoproductHintException(CollidingKeys(key, co.get(key)))
3✔
89
      case co: ConfigObject =>
1✔
90
        Map(key -> fieldValue(name)).toConfig.withFallback(co.toConfig)
3✔
UNCOV
91
      case _ =>
92
        throw CoproductHintException(WrongType(value.valueType, Set(ConfigValueType.OBJECT)))
×
93
    }
94
  }
95
}
96

97
object FieldCoproductHint {
98
  val defaultMapping: String => String = ConfigFieldMapping(PascalCase, KebabCase)
3✔
99
}
100

101
/** Hint where all coproduct options are tried in order. `from` will choose the first option able to deserialize the
102
  * config without errors, while `to` will write the config as is, with no disambiguation information.
103
  */
104
class FirstSuccessCoproductHint[A] extends CoproductHint[A] {
105
  def from(cursor: ConfigCursor, options: Seq[String]): ConfigReader.Result[CoproductHint.Action] =
1✔
106
    Right(
3✔
107
      Attempt(
2✔
108
        cursor,
109
        options,
110
        failures =>
UNCOV
111
          cursor.asConfigValue
112
            .fold(identity, v => ConfigReaderFailures(cursor.failureFor(NoValidCoproductOptionFound(v, failures))))
×
113
      )
114
    )
115

116
  def to(value: ConfigValue, name: String): ConfigValue =
1✔
117
    value
118
}
119

120
object CoproductHint {
121

122
  /** What should be done when reading a given coproduct option.
123
    */
124
  sealed trait Action {
125

126
    /** The `ConfigCursor` to use when trying to read the coproduct option.
127
      */
128
    def cursor: ConfigCursor
129
  }
130

131
  /** An action to only use the provided `ConfigCursor` and not try other options.
132
    *
133
    * @param cursor
134
    *   the `ConfigCursor` to use when reading the coproduct option
135
    * @param option
136
    *   the coproduct option to consider when reading from the provider cursor
137
    */
138
  case class Use(cursor: ConfigCursor, option: String) extends Action
139

140
  /** An action to attempt to use the provided coproduct options, in the specified order, stopping at the first one that
141
    * reads successfully.
142
    *
143
    * @param cursor
144
    *   the `ConfigCursor` to use when reading the coproduct option
145
    * @param options
146
    *   the coproduct options to attempt reading, in order
147
    * @param combineFailures
148
    *   the function to combine all failures in case all attempts to read fail
149
    */
150
  case class Attempt(
151
      cursor: ConfigCursor,
152
      options: Seq[String],
153
      combineFailures: Seq[(String, ConfigReaderFailures)] => ConfigReaderFailures
154
  ) extends Action
155

156
  implicit def default[A]: CoproductHint[A] = new FieldCoproductHint[A]("type")
3✔
157
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc