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

pureconfig / pureconfig / 17584197229

09 Sep 2025 01:27PM UTC coverage: 94.027% (-0.7%) from 94.686%
17584197229

Pull #1839

web-flow
Merge f6adfff74 into ae9e60568
Pull Request #1839: Additional mode for semiauto derivation

6 of 6 new or added lines in 2 files covered. (100.0%)

4 existing lines in 3 files now uncovered.

2377 of 2528 relevant lines covered (94.03%)

2.6 hits per line

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

91.84
/core/src/main/scala/pureconfig/ConfigSource.scala
1
package pureconfig
2

3
import java.io.File
4
import java.net.URL
5
import java.nio.file.Path
6

7
import scala.reflect.ClassTag
8

9
import com.typesafe.config._
10

11
import pureconfig.ConfigReader.Result
12
import pureconfig.backend.ConfigWrapper._
13
import pureconfig.backend.{ConfigFactoryWrapper, PathUtil}
14
import pureconfig.error.{CannotRead, ConfigReaderException, ConfigReaderFailures}
15

16
/** A representation of a source from which `ConfigValue`s can be loaded, such as a file or a URL.
17
  *
18
  * A source allows users to load configs from this source as any type for which a `ConfigReader` is available. Raw
19
  * configs can also be retrieved as a `ConfigValue`, a `ConfigCursor` or a `FluentConfigCursor`. Before using any of
20
  * the loading methods described, Users can opt to focus on a specific part of a config by specifying a namespace.
21
  *
22
  * All config loading methods are lazy and defer resolution of references until needed.
23
  */
24
trait ConfigSource {
25

26
  /** Retrieves a `ConfigValue` from this source. This forces the config to be resolved, if needed.
27
    *
28
    * @return
29
    *   a `ConfigValue` retrieved from this source.
30
    */
31
  def value(): Result[ConfigValue]
32

33
  /** Returns a cursor for a `ConfigValue` retrieved from this source.
34
    *
35
    * @return
36
    *   a cursor for a `ConfigValue` retrieved from this source.
37
    */
38
  def cursor(): Result[ConfigCursor] =
1✔
39
    value().map(ConfigCursor(_, Nil))
2✔
40

41
  /** Returns a fluent cursor for a `ConfigValue` retrieved from this source.
42
    *
43
    * @return
44
    *   a fluent cursor for a `ConfigValue` retrieved from this source.
45
    */
46
  def fluentCursor(): FluentConfigCursor =
1✔
47
    FluentConfigCursor(cursor())
5✔
48

49
  /** Navigates through the config to focus on a namespace.
50
    *
51
    * @param namespace
52
    *   the namespace to focus on
53
    * @return
54
    *   a new `ConfigSource` focused on the given namespace.
55
    */
56
  def at(namespace: String): ConfigSource =
1✔
57
    ConfigSource.fromCursor(fluentCursor().at(PathUtil.splitPath(namespace).map(p => p: PathSegment): _*))
5✔
58

59
  /** Loads a configuration of type `A` from this source.
60
    *
61
    * @tparam A
62
    *   the type of the config to be loaded
63
    * @return
64
    *   A `Right` with the configuration if it is possible to create an instance of type `A` from this source, a
65
    *   `Failure` with details on why it isn't possible otherwise
66
    */
67
  final def load[A](implicit reader: ConfigReader[A]): Result[A] = cursor().flatMap(reader.from)
4✔
68

69
  /** Loads a configuration of type `A` from this source. If it is not possible to create an instance of `A`, this
70
    * method throws a `ConfigReaderException`.
71
    *
72
    * @tparam A
73
    *   the type of the config to be loaded
74
    * @return
75
    *   The configuration of type `A` loaded from this source.
76
    */
77
  @throws[ConfigReaderException[_]]
78
  final def loadOrThrow[A: ClassTag](implicit reader: ConfigReader[A]): A = {
1✔
79
    load[A] match {
3✔
80
      case Right(config) => config
2✔
81
      case Left(failures) => throw new ConfigReaderException[A](failures)
3✔
82
    }
83
  }
84
}
85

86
/** A `ConfigSource` which is guaranteed to generate config objects (maps) as root values.
87
  *
88
  * @param getConf
89
  *   the thunk to generate a `Config` instance. This parameter won't be memoized so it can be used with dynamic sources
90
  *   (e.g. URLs)
91
  */
92
final class ConfigObjectSource private (getConf: () => Result[Config]) extends ConfigSource {
93

94
  def value(): Result[ConfigObject] =
1✔
95
    config().flatMap(_.resolveSafe()).map(_.root)
4✔
96

97
  // Avoids unnecessary cast on `ConfigCursor#asObjectCursor`.
98
  override def cursor(): Result[ConfigCursor] =
1✔
99
    value().map(ConfigObjectCursor(_, Nil))
4✔
100

101
  /** Reads a `Config` from this config source. The returned config is usually unresolved, unless the source forces it
102
    * otherwise.
103
    *
104
    * @return
105
    *   a `Config` provided by this source.
106
    */
107
  def config(): Result[Config] =
1✔
108
    getConf()
5✔
109

110
  /** Merges this source with another one, with the latter being used as a fallback (e.g. the source on which this
111
    * method is called takes priority). Both sources are required to produce a config object successfully.
112
    *
113
    * @param cs
114
    *   the config source to use as fallback
115
    * @return
116
    *   a new `ConfigObjectSource` that loads configs from both sources and uses `cs` as a fallback for this source
117
    */
118
  def withFallback(cs: ConfigObjectSource): ConfigObjectSource =
1✔
119
    ConfigObjectSource(Result.zipWith(config(), cs.config())(_.withFallback(_)))
4✔
120

121
  /** Returns a `ConfigObjectSource` that provides the same config as this one, but falls back to providing an empty
122
    * config when the source cannot be read. It can be used together with `.withFallback` to specify optional config
123
    * files to be merged (like `reference.conf`).
124
    *
125
    * @return
126
    *   a new `ConfigObjectSource` that provides the same config as this one, but falls back to an empty config if it
127
    *   cannot be read.
128
    */
129
  def optional: ConfigObjectSource =
1✔
130
    recoverWith { case ConfigReaderFailures(_: CannotRead) => ConfigSource.empty }
2✔
131

132
  /** Applies a function `f` if this source returns a failure, returning an alternative config source in those cases.
133
    *
134
    * @param f
135
    *   the function to apply if this source returns a failure
136
    * @return
137
    *   a new `ConfigObjectSource` that provides an alternative config in case this source fails
138
    */
139
  def recoverWith(f: PartialFunction[ConfigReaderFailures, ConfigObjectSource]): ConfigObjectSource =
1✔
140
    ConfigObjectSource(getConf().left.flatMap { failures =>
2✔
141
      f.lift(failures) match {
1✔
142
        case None => Left(failures)
2✔
143
        case Some(source) => source.config()
2✔
144
      }
145
    })
146
}
147

148
object ConfigObjectSource {
149

150
  /** Creates a `ConfigObjectSource` from a `Result[Config]`. The provided argument is allowed to change value over
151
    * time.
152
    *
153
    * @param conf
154
    *   the config to be provided by this source
155
    * @return
156
    *   a `ConfigObjectSource` providing the given config.
157
    */
158
  def apply(conf: => Result[Config]): ConfigObjectSource =
1✔
159
    new ConfigObjectSource(() => conf)
7✔
160
}
161

162
/** Object containing factory methods for building `ConfigSource`s.
163
  *
164
  * The sources provided here use Typesafe Config configs created from files, resources, URLs or strings. It also
165
  * provides sources that delegate the loading component to Typesafe Config, to leverage reference configs and
166
  * overrides, making it easy to switch from using `ConfigFactory` to `ConfigSource`.
167
  *
168
  * Other PureConfig modules may provide other ways or building config sources (e.g. for different config formats or
169
  * data sources).
170
  */
171
object ConfigSource {
172

173
  /** A config source for the default loading process in Typesafe Config. Typesafe Config stacks `reference.conf`
174
    * resources provided by libraries, application configs (by default `application.conf` in resources) and system
175
    * property overrides, resolves them and merges them into a single config. This source is equivalent to
176
    * `defaultOverrides.withFallback(defaultApplication).withFallback(defaultReference)`.
177
    */
178
  val default = ConfigObjectSource(ConfigFactoryWrapper.load())
7✔
179

180
  /** A config source for the default loading process in Typesafe Config with a custom application config source.
181
    * Typesafe Config stacks `reference.conf` resources provided by libraries, the given file and system property
182
    * overrides, resolves them and merges them into a single config.
183
    *
184
    * This method is provided here to support use cases that previously depended on `ConfigFactory.load(config)`.
185
    * Creating a custom source by merging the layers manually is usually recommended as it makes the config priorities
186
    * more transparent.
187
    *
188
    * @param appSource
189
    *   the source providing the application config
190
    * @return
191
    *   a `ConfigObjectSource` for the default loading process in Typesafe Config with a custom application config
192
    *   source.
193
    */
194
  def default(appSource: ConfigObjectSource): ConfigObjectSource =
1✔
195
    ConfigObjectSource(appSource.config().flatMap(ConfigFactoryWrapper.load))
2✔
196

197
  /** A config source that always provides empty configs.
198
    */
199
  val empty = ConfigObjectSource(Right(ConfigFactory.empty))
3✔
200

201
  /** A config source for the default reference config in Typesafe Config (`reference.conf` resources provided by
202
    * libraries). Like Typesafe Config, it provides an empty object if `reference.conf` files are not found.
203
    *
204
    * As required by
205
    * [[https://github.com/lightbend/config/blob/master/HOCON.md#conventional-configuration-files-for-jvm-apps the HOCON spec]],
206
    * the default reference files are pre-emptively resolved - substitutions in the reference config aren't affected by
207
    * application configs.
208
    */
209
  val defaultReference = ConfigObjectSource(ConfigFactoryWrapper.defaultReference())
6✔
210

211
  /** A config source for the default reference config in Typesafe Config (`reference.conf` resources provided by
212
    * libraries) before being resolved. This can be used as an alternative to `defaultReference` for use cases that
213
    * require `reference.conf` to depend on `application.conf`. Like Typesafe Config, it provides an empty object if
214
    * `reference.conf` files are not found.
215
    */
216
  val defaultReferenceUnresolved = resources("reference.conf").optional
7✔
217

218
  /** A config source for the default application config in Typesafe Config (by default `application.conf` in
219
    * resources). Like Typesafe Config, it provides an empty object if application config files are not found.
220
    */
221
  val defaultApplication = ConfigObjectSource(ConfigFactoryWrapper.defaultApplication())
7✔
222

223
  /** A config source for the default overrides in Typesafe Config (by default a map of system properties).
224
    */
UNCOV
225
  val defaultOverrides = ConfigObjectSource(ConfigFactoryWrapper.defaultOverrides())
×
226

227
  /** A config source for Java system properties.
228
    */
229
  val systemProperties = ConfigObjectSource(ConfigFactoryWrapper.systemProperties())
×
230

231
  /** Returns a config source that provides configs read from a file.
232
    *
233
    * @param path
234
    *   the path to the file as a string
235
    * @return
236
    *   a config source that provides configs read from a file.
237
    */
238
  def file(path: String) = ConfigObjectSource(ConfigFactoryWrapper.parseFile(new File(path)))
2✔
239

240
  /** Returns a config source that provides configs read from a file.
241
    *
242
    * @param path
243
    *   the path to the file
244
    * @return
245
    *   a config source that provides configs read from a file.
246
    */
247
  def file(path: Path) = ConfigObjectSource(ConfigFactoryWrapper.parseFile(path.toFile))
4✔
248

249
  /** Returns a config source that provides configs read from a file.
250
    *
251
    * @param file
252
    *   the file
253
    * @return
254
    *   a config source that provides configs read from a file.
255
    */
256
  def file(file: File) = ConfigObjectSource(ConfigFactoryWrapper.parseFile(file))
×
257

258
  /** Returns a config source that provides configs read from a URL. The URL can either point to a local file or to a
259
    * remote HTTP location.
260
    *
261
    * @param url
262
    *   the URL
263
    * @return
264
    *   a config source that provides configs read from a URL.
265
    */
266
  def url(url: URL) = ConfigObjectSource(ConfigFactoryWrapper.parseURL(url))
2✔
267

268
  /** Returns a config source that provides configs read from JVM resource files. If multiple files are found, they are
269
    * merged in no specific order. This method uses Typesafe Config's default class loader
270
    * (`Thread.currentThread().getContextClassLoader()`).
271
    *
272
    * @param name
273
    *   the resource name
274
    * @return
275
    *   a config source that provides configs read from JVM resource files.
276
    */
277
  def resources(name: String) =
1✔
278
    ConfigObjectSource(ConfigFactoryWrapper.parseResources(name, null))
7✔
279

280
  /** Returns a config source that provides configs read from JVM resource files. If multiple files are found, they are
281
    * merged in no specific order. The given class loader will be used to look for resources.
282
    *
283
    * @param name
284
    *   the resource name
285
    * @param classLoader
286
    *   the class loader to use to look for resources
287
    * @return
288
    *   a config source that provides configs read from JVM resource files.
289
    */
290
  def resources(name: String, classLoader: ClassLoader) =
291
    ConfigObjectSource(ConfigFactoryWrapper.parseResources(name, classLoader))
×
292

293
  /** Returns a config source that provides a config parsed from a string.
294
    *
295
    * @param confStr
296
    *   the config content
297
    * @return
298
    *   a config source that provides a config parsed from a string.
299
    */
300
  def string(confStr: String) = ConfigObjectSource(ConfigFactoryWrapper.parseString(confStr))
3✔
301

302
  /** Returns a config source that provides a fixed `Config`.
303
    *
304
    * @param conf
305
    *   the config to be provided
306
    * @return
307
    *   a config source that provides the given config.
308
    */
309
  def fromConfig(conf: Config) = ConfigObjectSource(Right(conf))
3✔
310

311
  /** Creates a `ConfigSource` from a `ConfigCursor`.
312
    *
313
    * @param cur
314
    *   the cursor to be provided by this source
315
    * @return
316
    *   a `ConfigSource` providing the given cursor.
317
    */
318
  private[pureconfig] def fromCursor(cur: ConfigCursor): ConfigSource =
319
    new ConfigSource {
320
      def value(): Result[ConfigValue] = cur.asConfigValue
321
      override def cursor(): Result[ConfigCursor] = Right(cur)
322
    }
323

324
  /** Creates a `ConfigSource` from a `FluentConfigCursor`.
325
    *
326
    * @param cur
327
    *   the cursor to be provided by this source
328
    * @return
329
    *   a `ConfigSource` providing the given cursor.
330
    */
331
  private[pureconfig] def fromCursor(cur: FluentConfigCursor): ConfigSource =
1✔
332
    new ConfigSource {
4✔
333
      def value(): Result[ConfigValue] = cur.cursor.flatMap(_.asConfigValue)
334
      override def cursor(): Result[ConfigCursor] = cur.cursor
3✔
335
      override def fluentCursor(): FluentConfigCursor = cur
336
    }
337
}
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