• 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

92.45
/core/src/main/scala/pureconfig/ConfigReader.scala
1
package pureconfig
2

3
import scala.collection.{Factory, mutable}
4
import scala.reflect.ClassTag
5
import scala.util.Try
6

7
import com.typesafe.config.ConfigValue
8

9
import pureconfig.ConvertHelpers._
10
import pureconfig.error.{ConfigReaderFailure, ConfigReaderFailures, FailureReason, UserValidationFailed}
11

12
/** Trait for objects capable of reading objects of a given type from `ConfigValues`.
13
  *
14
  * @tparam A
15
  *   the type of objects readable by this `ConfigReader`
16
  */
17
trait ConfigReader[A] {
18
  import pureconfig.ConfigReader._
19

20
  /** Convert the configuration given by a cursor into an instance of `A` if possible.
21
    *
22
    * @param cur
23
    *   The cursor from which the config should be loaded
24
    * @return
25
    *   either a list of failures or an object of type `A`
26
    */
27
  def from(cur: ConfigCursor): ConfigReader.Result[A]
28

29
  /** Convert the given configuration into an instance of `A` if possible.
30
    *
31
    * @param config
32
    *   The configuration from which the config should be loaded
33
    * @return
34
    *   either a list of failures or an object of type `A`
35
    */
36
  def from(config: ConfigValue): ConfigReader.Result[A] =
1✔
37
    from(ConfigCursor(config, Nil))
21✔
38

39
  /** Maps a function over the results of this reader.
40
    *
41
    * @param f
42
    *   the function to map over this reader
43
    * @tparam B
44
    *   the output type of the function
45
    * @return
46
    *   a `ConfigReader` returning the results of this reader mapped by `f`.
47
    */
48
  def map[B](f: A => B): ConfigReader[B] =
1✔
49
    fromCursor[B] { cur => from(cur).flatMap { v => cur.scopeFailure(toResult(f)(v)) } }
10✔
50

51
  /** Maps a function that can possibly fail over the results of this reader.
52
    *
53
    * @param f
54
    *   the function to map over this reader
55
    * @tparam B
56
    *   the value read by the function in case of success
57
    * @return
58
    *   a `ConfigReader` returning the results of this reader mapped by `f`, with the resulting `Either` flattened as a
59
    *   success or failure.
60
    */
61
  def emap[B](f: A => Either[FailureReason, B]): ConfigReader[B] =
1✔
62
    fromCursor[B] { cur => from(cur).flatMap { v => cur.scopeFailure(f(v)) } }
29✔
63

64
  /** Monadically bind a function over the results of this reader.
65
    *
66
    * @param f
67
    *   the function to bind over this reader
68
    * @tparam B
69
    *   the type of the objects readable by the resulting `ConfigReader`
70
    * @return
71
    *   a `ConfigReader` returning the results of this reader bound by `f`.
72
    */
73
  def flatMap[B](f: A => ConfigReader[B]): ConfigReader[B] =
1✔
74
    fromCursor[B] { cur => from(cur).flatMap(f(_).from(cur)) }
3✔
75

76
  /** Combines this reader with another, returning both results as a pair.
77
    *
78
    * @param reader
79
    *   the reader to combine with this one
80
    * @tparam B
81
    *   the type of the objects readable by the provided reader
82
    * @return
83
    *   a `ConfigReader` returning the results of both readers as a pair.
84
    */
85
  def zip[B](reader: ConfigReader[B]): ConfigReader[(A, B)] =
1✔
86
    fromCursor[(A, B)] { cur =>
3✔
87
      (from(cur), reader.from(cur)) match {
1✔
88
        case (Right(a), Right(b)) => Right((a, b))
3✔
89
        case (Left(fa), Right(_)) => Left(fa)
3✔
90
        case (Right(_), Left(fb)) => Left(fb)
3✔
91
        case (Left(fa), Left(fb)) => Left(fa ++ fb)
3✔
92
      }
93
    }
94

95
  /** Combines this reader with another, returning the result of the first one that succeeds.
96
    *
97
    * @param reader
98
    *   the reader to combine with this one
99
    * @tparam AA
100
    *   the type of the objects readable by both readers
101
    * @return
102
    *   a `ConfigReader` returning the results of this reader if it succeeds and the results of `reader` otherwise.
103
    */
104
  def orElse[AA >: A, B <: AA](reader: => ConfigReader[B]): ConfigReader[AA] =
1✔
105
    fromCursor[AA] { cur =>
2✔
106
      from(cur) match {
1✔
107
        case Right(a) => Right(a)
2✔
108
        case Left(failures) => reader.from(cur).left.map(failures ++ _)
2✔
109
      }
110
    }
111

112
  /** Applies a function to configs before passing them to this reader.
113
    *
114
    * @param f
115
    *   the function to apply to input configs
116
    * @return
117
    *   a `ConfigReader` returning the results of this reader when the input configs are mapped using `f`.
118
    */
119
  def contramapConfig(f: ConfigValue => ConfigValue): ConfigReader[A] =
1✔
120
    fromCursor[A] { cur => from(ConfigCursor(cur.valueOpt.map(f), cur.pathElems)) }
2✔
121

122
  /** Applies a function to config cursors before passing them to this reader.
123
    *
124
    * @param f
125
    *   the function to apply to input config cursors
126
    * @return
127
    *   a `ConfigReader` returning the results of this reader when the input cursors are mapped using `f`.
128
    */
UNCOV
129
  def contramapCursor(f: ConfigCursor => ConfigCursor): ConfigReader[A] =
130
    fromCursor[A] { cur => from(f(cur)) }
×
131

132
  /** Fails the reader if the condition does not hold for the result.
133
    *
134
    * @param pred
135
    *   the condition to assert
136
    * @param message
137
    *   the failed validation message
138
    * @return
139
    *   a `ConfigReader` returning the results of this reader if the condition holds or a failed reader otherwise.
140
    */
141
  def ensure(pred: A => Boolean, message: A => String): ConfigReader[A] =
1✔
142
    emap(a => Either.cond(pred(a), a, UserValidationFailed(message(a))))
2✔
143
}
144

145
/** Provides methods to create [[ConfigReader]] instances.
146
  */
147
object ConfigReader
148
    extends BasicReaders
149
    with CollectionReaders
150
    with ProductReaders
151
    with ExportedReaders
152
    with ReaderDerives {
153

154
  /** The type of most config PureConfig reading methods.
155
    *
156
    * @tparam A
157
    *   the type of the result
158
    */
159
  type Result[A] = Either[ConfigReaderFailures, A]
160

161
  /** Object containing useful constructors and utility methods for `Result`s.
162
    */
163
  object Result {
164

165
    /** Sequences a collection of `Result`s into a `Result` of a collection.
166
      */
167
    def sequence[A, CC[X] <: IterableOnce[X]](
1✔
168
        rs: CC[ConfigReader.Result[A]]
169
    )(implicit cbf: Factory[A, CC[A]]): ConfigReader.Result[CC[A]] = {
170
      rs.iterator
1✔
171
        .foldLeft[ConfigReader.Result[mutable.Builder[A, CC[A]]]](Right(cbf.newBuilder)) {
7✔
172
          case (Right(builder), Right(a)) => Right(builder += a)
7✔
173
          case (Left(err), Right(_)) => Left(err)
4✔
174
          case (Right(_), Left(err)) => Left(err)
5✔
175
          case (Left(errs), Left(err)) => Left(errs ++ err)
4✔
176
        }
177
        .map(_.result())
7✔
178
    }
179

180
    /** Merges two `Result`s using a given function.
181
      */
182
    def zipWith[A, B, C](first: ConfigReader.Result[A], second: ConfigReader.Result[B])(
1✔
183
        f: (A, B) => C
184
    ): ConfigReader.Result[C] =
185
      (first, second) match {
186
        case (Right(a), Right(b)) => Right(f(a, b))
8✔
187
        case (Left(aFailures), Left(bFailures)) => Left(aFailures ++ bFailures)
6✔
188
        case (_, l: Left[_, _]) => l.asInstanceOf[Left[ConfigReaderFailures, Nothing]]
6✔
189
        case (l: Left[_, _], _) => l.asInstanceOf[Left[ConfigReaderFailures, Nothing]]
5✔
190
      }
191

192
    /** Returns a `Result` containing a single failure.
193
      */
194
    def fail[A](failure: ConfigReaderFailure): ConfigReader.Result[A] = Left(ConfigReaderFailures(failure))
3✔
195
  }
196

197
  def apply[A](implicit reader: ConfigReader[A]): ConfigReader[A] = reader
1✔
198

199
  /** Creates a `ConfigReader` from a function reading a `ConfigCursor`.
200
    *
201
    * @param fromF
202
    *   the function used to read config cursors to values
203
    * @tparam A
204
    *   the type of the objects readable by the returned reader
205
    * @return
206
    *   a `ConfigReader` for reading objects of type `A` using `fromF`.
207
    */
208
  def fromCursor[A](fromF: ConfigCursor => ConfigReader.Result[A]) =
1✔
209
    new ConfigReader[A] {
28✔
210
      def from(cur: ConfigCursor) = fromF(cur)
22✔
211
    }
212

213
  /** Creates a `ConfigReader` from a function.
214
    *
215
    * @param fromF
216
    *   the function used to read configs to values
217
    * @tparam A
218
    *   the type of the objects readable by the returned reader
219
    * @return
220
    *   a `ConfigReader` for reading objects of type `A` using `fromF`.
221
    */
222
  def fromFunction[A](fromF: ConfigValue => ConfigReader.Result[A]) =
1✔
223
    fromCursor(_.asConfigValue.flatMap(fromF))
3✔
224

225
  def fromString[A](fromF: String => Either[FailureReason, A]): ConfigReader[A] =
1✔
226
    ConfigReader.fromCursor(_.asString).emap(fromF)
29✔
227

UNCOV
228
  def fromStringTry[A](fromF: String => Try[A])(implicit ct: ClassTag[A]): ConfigReader[A] = {
229
    fromString[A](tryF(fromF))
×
230
  }
231

UNCOV
232
  def fromStringOpt[A](fromF: String => Option[A])(implicit ct: ClassTag[A]): ConfigReader[A] = {
UNCOV
233
    fromString[A](optF(fromF))
2✔
234
  }
235

236
  def fromNonEmptyString[A](fromF: String => Either[FailureReason, A])(implicit ct: ClassTag[A]): ConfigReader[A] = {
1✔
237
    fromString(string => ensureNonEmpty(ct)(string).flatMap(s => fromF(s)))
29✔
238
  }
239

UNCOV
240
  def fromNonEmptyStringTry[A](fromF: String => Try[A])(implicit ct: ClassTag[A]): ConfigReader[A] = {
241
    fromNonEmptyString[A](tryF(fromF))
×
242
  }
243

UNCOV
244
  def fromNonEmptyStringOpt[A](fromF: String => Option[A])(implicit ct: ClassTag[A]): ConfigReader[A] = {
245
    fromNonEmptyString[A](optF(fromF))
×
246
  }
247
}
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