Skip to content

Commit

Permalink
Fix the handling of sequences of non-Config values. (#215)
Browse files Browse the repository at this point in the history
* Fix the handling of sequences of non-Config values.

* Fix partial method typing.

Don't throw an exception on error, just log a warning.
  • Loading branch information
jkinkead authored Jan 17, 2017
1 parent 0e0f3a7 commit abcaa15
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 1 deletion.
35 changes: 34 additions & 1 deletion guice/src/main/scala/org/allenai/common/guice/ConfigModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import com.typesafe.config.{
import net.codingwell.scalaguice.ScalaModule

import scala.collection.JavaConverters._
import scala.util.Try

/** Parent class for modules which use a typesafe config for values. This automatically binds all
* configuration values within a given Config instance, along with defaults from an optional
Expand Down Expand Up @@ -152,7 +153,37 @@ class ConfigModule(config: Config) extends ScalaModule with Logging {
case ConfigValueType.STRING =>
bindConfigKey[String](fullPath)
case ConfigValueType.LIST =>
bindConfigKey[Seq[Config]](fullPath)
// Figure out the list subtype. Note that there is no API call to handle this, so we try
// methods in serial until one succeeds.
val methods: Seq[() => Unit] = Seq(
() => {
fullConfig.apply[Seq[Config]](key)
bindConfigKey[Seq[Config]](key)
},
() => {
// Scala compiles a type in a constructor of Seq[Double] to Seq[Object], meaning we
// need to bind as Seq[Object] in order for Guice to work.
val value = fullConfig[Seq[Double]](key).asInstanceOf[Seq[Object]]
bind[Seq[Object]].annotatedWithName(key).toInstance(value)
bind[Option[Seq[Object]]].annotatedWithName(key).toInstance(Some(value))
},
() => {
val value = fullConfig.apply[Seq[Boolean]](key).asInstanceOf[Seq[Object]]
bind[Seq[Object]].annotatedWithName(key).toInstance(value)
bind[Option[Seq[Object]]].annotatedWithName(key).toInstance(Some(value))
},
() => {
// All values will parse as strings, which is odd, so this is last.
fullConfig.apply[Seq[String]](key)
bindConfigKey[Seq[String]](key)
}
)
// Lazily apply the first method that works.
val success = methods.iterator.map(method => Try(method())).exists(_.isSuccess)
if (!success) {
logger.warn(s"Could not find list type for key '$key' in in " +
s"${getClass.getSimpleName}. No value will be bound to '$key'.")
}
case ConfigValueType.OBJECT =>
bindConfigKey[Config](fullPath)
// Recurse.
Expand All @@ -176,6 +207,8 @@ class ConfigModule(config: Config) extends ScalaModule with Logging {
bind[Option[Double]].annotatedWith(classOf[Named]).toInstance(None)
bind[Option[Config]].annotatedWith(classOf[Named]).toInstance(None)
bind[Option[Seq[Config]]].annotatedWith(classOf[Named]).toInstance(None)
bind[Option[Seq[String]]].annotatedWith(classOf[Named]).toInstance(None)
bind[Option[Seq[Object]]].annotatedWith(classOf[Named]).toInstance(None)
bind[Option[String]].annotatedWith(classOf[Named]).toInstance(None)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,14 @@ case class DottedKeys @Inject() (
@Named("\"i.have.more.dots\".bar") bar: Int
)

// Test class with Seq values.
case class SeqValues @Inject() (
@Named("seqOfConfig") configs: Seq[Config],
@Named("seqOfString") strings: Seq[String],
@Named("seqOfBool") booleans: Seq[Boolean],
@Named("seqOfDouble") doubles: Seq[Double]
)

class ConfigModuleSpec extends UnitSpec {
"bindConfig" should "bind config values to appropriate @Named bindings" in {
// Config with an entry for all of the bindable values except the one with a default.
Expand Down Expand Up @@ -223,4 +231,21 @@ class ConfigModuleSpec extends UnitSpec {

val instance = injector.getInstance(classOf[DottedKeys])
}

it should "handle sequences" in {
val testConfig = ConfigFactory.parseString("""
seqOfConfig = [ {a: "a"}, {b: "b"} ]
seqOfString = [ "foo", "bar" ]
seqOfBool = [ true, false, true ]
seqOfDouble = [ 1, 2 ]
""")
val testModule = new ConfigModule(testConfig)

val injector = Guice.createInjector(testModule)

val instance = injector.getInstance(classOf[SeqValues])
instance.strings shouldBe Seq("foo", "bar")
instance.booleans shouldBe Seq(true, false, true)
instance.doubles shouldBe Seq(1.0, 2.0)
}
}

0 comments on commit abcaa15

Please sign in to comment.