I have my entry point like this:
def main(args: Array[String]): Unit = {
pureconfig.loadConfig[Conf] match {
case Right(conf) => doStuff(conf)
case Left(fail) => lectureUserAboutStuff(fail)
}
}
So it looks like the main purpose of my program is to load a configuration, which is not true, the core of my program is to doStuff
.
How to expressively represent this and hide undercover the configuration?
I would like something like this:
def main(args: Array[String]): Unit = {
doStuff(conf)(failHandler)
}
In which is clear that the failure handling and the configuration loading is just accessory.
One of the most common way to use PureConfig from the main
method of your program is to fail fast in case of error. The API provides loadConfigOrThrow
which returns the configuration if the loading has been successful and throws a ConfigReaderException
otherwise:
import pureconfig.loadConfigOrThrow
def main(args: Array[String]): Unit = {
val conf = loadConfigOrThrow[Conf] // will throw if Conf cannot be loaded
doStuff(conf)
}
The reason why pureconfig.loadConfig
returns an Either
is because
loading configuration can fail. Either
models both the failure
case, with Left
, and the success case, with Right
. This is
good for a library, because you never know where loadConfig
will be called and consequently you don't want to throw an
exception. While this is true in general, when loadConfig
is
used from a "surface" method, e.g. main
, throwing exception or
just exiting in case of config loading failure makes sense and
that's why pureconfig provides loadConfigOrThrow
.
In case you don't like throwing exception or you prefer a custom
failure handler, like lectureUserAboutStuff
, you can create a
helper:
def loadConfig(): Option[Conf] = {
val errorOrConf = pureconfig.loadConfig[Conf]
errorOrConf.left.foreach(lectureUserAboutStuff)
errorOrConf.toOption
}
def main(args: Array[String]): Unit =
loadConfig().foreach(doStuff)
One last thing to keep in mind is that a minor issue with this
code is that the exit code of the application is not affected by
configuration failures. If you want to set a exit code for when
the configuration cannot be loaded, then change main
to
val configReaderFailureErrorCode = 42
private def configFailureExit() =
sys.exit(configReaderFailureErrorCode)
def main(args: Array[String]): Unit =
loadConfig().fold(configFailureExit())(doStuff)