circe-config
circe-config copied to clipboard
circe-config will fail on Boolean values written as "on/true/false/off"
It may be intended, but right now circe-config is inconsistent with original config for Boolean values written as strings:
scala> import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigFactory
scala> import io.circe.config.parser
import io.circe.config.parser
scala> val config = ConfigFactory.parseString("server { host = localhost, port = 8080 }")
config: com.typesafe.config.Config = Config(SimpleConfigObject({"server":{"host":"localhost","port":8080}}))
scala> val config = ConfigFactory.parseString("server { host = localhost, port = 8080, enabled = on }")
config: com.typesafe.config.Config = Config(SimpleConfigObject({"server":{"enabled":"on","host":"localhost","port":8080}}))
scala> val json: Either[io.circe.ParsingFailure, io.circe.Json] = parser.parse(config)
json: Either[io.circe.ParsingFailure,io.circe.Json] =
Right({
"server" : {
"enabled" : "on",
"port" : 8080,
"host" : "localhost"
}
})
scala> config.getBoolean("server.enabled")
res0: Boolean = true
It is possible to work around it, but it is not very convenient when passing config values via ENV_VARIABLES in K8s -- it disallows unquoted strings. :(
The problem is that com.typesafe.config.Config is converted to io.circe.Json without knowing what will be the final type of the "on" string. As far as I know it is not possible to introspect the structure of the type when it is being decoded.
The only solution I can think of is to create a wrapper "boolean' type which allows to use an implicit convertion to translate from string to boolean as Typesafe config does.
import scala.language.implicitConversions
import org.scalatest.{ FlatSpec, Matchers }
import com.typesafe.config.ConfigFactory
import io.circe._
import io.circe.generic.auto._
import io.circe.config.syntax._
object ConfigBooleanSpec {
case class ConfigBoolean(value: Boolean)
object ConfigBoolean {
implicit def toBoolean(b: ConfigBoolean): Boolean = b.value
}
implicit val configBooleanDecoder: Decoder[ConfigBoolean] = {
val truthful = Set("true", "yes", "on")
Decoder.decodeString.map(s => ConfigBoolean(truthful(s)))
}
case class ServerConfig(host: String, port: Int, enabled: ConfigBoolean)
}
class ConfigBooleanSpec extends FlatSpec with Matchers {
import ConfigBooleanSpec._
"custom boolean" should "parse and decode from string" in {
val config = ConfigFactory.parseString(
"""
host = localhost
port = 8080
enabled = on
""")
val Right(ServerConfig(_, _, enabled)) = config.as[ServerConfig]
assert(enabled.value)
assert(enabled)
}
}
It would be good to document this though so if you are up for making a PR that would be great else let's keep this issue open until that gets fixed.
Sorry for the late answer, ConfigBoolean is a possible solution, but it will not work with external/legacy entities with Boolean attributes or providing their own Decoder instance. Or if you just dont want to leak circe-config details in your domain model.
I think better solution would be to delay conversion to Json as much as possible or even do not convert at all, but it would require some kind of JsonDelegate instance that does nothing but delegates fold(..) and asNull|asBoolean|.. methods to it's underlying value. And I don't think it's possible with current circe as Json is sealed.
Maybe something could be done via ConfigCursor version of HCursor for Decoder to use?
I agree that would be optimal but not sure it is possible to do this sort of lazy conversion.