From 57de54d8b369b49912dc983c402d52694936a395 Mon Sep 17 00:00:00 2001 From: Chris Frohoff Date: Wed, 13 May 2015 11:27:59 -0700 Subject: [PATCH] working member-based settings --- .../ficus/readers/ArbitraryTypeReader.scala | 56 +++++++------------ .../readers/ArbitraryTypeReaderSpec.scala | 36 +++++++++--- 2 files changed, 46 insertions(+), 46 deletions(-) diff --git a/src/main/scala/net/ceedubs/ficus/readers/ArbitraryTypeReader.scala b/src/main/scala/net/ceedubs/ficus/readers/ArbitraryTypeReader.scala index 7b291e7..ec48fa5 100644 --- a/src/main/scala/net/ceedubs/ficus/readers/ArbitraryTypeReader.scala +++ b/src/main/scala/net/ceedubs/ficus/readers/ArbitraryTypeReader.scala @@ -9,58 +9,34 @@ import scala.annotation.StaticAnnotation import scala.collection.JavaConversions._ trait ArbitraryTypeReader { - @ArbitraryTypeReader.settings(identity, false) implicit def arbitraryTypeValueReader[T]: ValueReader[T] = macro ArbitraryTypeReaderMacros.arbitraryTypeValueReader[T] -} - -object ArbitraryTypeReader extends ArbitraryTypeReader { - class settings(paramKeyMapper: String => String = identity[String], exhaustiveMapping: Boolean = false) extends StaticAnnotation -} - -trait OtherArbitraryTypeReader { - @ArbitraryTypeReader.settings(_.toLowerCase, true) - implicit def arbitraryTypeValueReader[T]: ValueReader[T] = macro ArbitraryTypeReaderMacros.arbitraryTypeValueReader[T] -} - -object OtherArbitraryTypeReader extends OtherArbitraryTypeReader - -object ArbitraryTypeReaderMacros { - import scala.reflect.macros.blackbox.Context - + val checkExhaustivity: Boolean = false + def mapParam(n: String): String = n def assertExhaustive(cfg: Config, path: String, mappedPaths: Seq[String]): Unit = { val relevantPaths = cfg.getObject(path).keySet.map(k => s"$path.$k") val unmappedPaths = relevantPaths -- mappedPaths if (! unmappedPaths.isEmpty) throw new IllegalArgumentException(s"config paths not exhaustively mapped: $unmappedPaths") } +} - def arbitraryTypeValueReader[T : c.WeakTypeTag](c: Context): c.Expr[ValueReader[T]] = { - import c.universe._ +object ArbitraryTypeReader extends ArbitraryTypeReader - try { - val annotationParamExpr - = c.macroApplication.symbol.annotations - .filter(_.tree.tpe <:< typeOf[ArbitraryTypeReader.settings]).head.tree.children.tail +object ArbitraryTypeReaderMacros { + import scala.reflect.macros.blackbox.Context - val paramMapperExpr :: exhaustiveMappingExpr :: Nil = annotationParamExpr - //println(paramMapperExpr) + def arbitraryTypeValueReader[T : c.WeakTypeTag](c: Context): c.Expr[ValueReader[T]] = { + import c.universe._ val readerExpr = reify { new ValueReader[T] { - val paramMapper: String => String = c.Expr[String => String](c.resetLocalAttrs(paramMapperExpr)).splice - val exhaustiveMapping: Boolean = c.Expr[Boolean](c.resetLocalAttrs(exhaustiveMappingExpr)).splice def read(config: Config, path: String): T = instantiateFromConfig[T](c)( config = c.Expr[Config](Ident(TermName("config"))), path = c.Expr[String](Ident(TermName("path")))).splice } } - //println(show(readerExpr)) - readerExpr - } catch { // FIXME: remove - case e => e.printStackTrace(); throw e - } } def instantiateFromConfig[T : c.WeakTypeTag](c: Context)(config: c.Expr[Config], path: c.Expr[String]): c.Expr[T] = { @@ -85,11 +61,15 @@ object ArbitraryTypeReaderMacros { ).map(Ident(_)).getOrElse(New(Ident(returnType.typeSymbol))) val instantiationCall = Select(instantiationObject, instantiationMethod.name) - val assertion = reify { - assertExhaustive(config.splice, path.splice, c.Expr[List[String]](q"List(..$instantiationKeys)").splice) - } + val enclosingObject = c.Expr[ArbitraryTypeReader](c.prefix.tree) - val conditionalAssertion = If(Ident(TermName("exhaustiveMapping")), assertion.tree, Literal(Constant(()))) + val keyListExpr = c.Expr[List[String]](q"List(..$instantiationKeys)") + + val conditionalAssertion = reify { + if (enclosingObject.splice.checkExhaustivity) { + enclosingObject.splice.assertExhaustive(config.splice, path.splice, keyListExpr.splice) + } + }.tree val instantiation = Apply(instantiationCall, instantiationArgs) c.Expr[T](Block(List(conditionalAssertion), instantiation)) } @@ -102,9 +82,11 @@ object ArbitraryTypeReaderMacros { if (!method.isPublic) fail(s"'$decodedMethodName' method is not public") + val enclosingObject = c.Expr[ArbitraryTypeReader](c.prefix.tree) + method.paramLists.head.zipWithIndex map { case (param, index) => val name = param.name.decodedName.toString - val key = q"""$path + "." + paramMapper($name)""" + val key = reify { path.splice + "." + enclosingObject.splice.mapParam(c.Expr[String](q"$name").splice) }.tree val returnType: Type = param.typeSignatureIn(c.weakTypeOf[T]) (companionObjectMaybe.filter(_ => param.asTerm.isParamWithDefault) map { companionObject => diff --git a/src/test/scala/net/ceedubs/ficus/readers/ArbitraryTypeReaderSpec.scala b/src/test/scala/net/ceedubs/ficus/readers/ArbitraryTypeReaderSpec.scala index 2ce0696..2c2b5dc 100644 --- a/src/test/scala/net/ceedubs/ficus/readers/ArbitraryTypeReaderSpec.scala +++ b/src/test/scala/net/ceedubs/ficus/readers/ArbitraryTypeReaderSpec.scala @@ -14,7 +14,8 @@ class ArbitraryTypeReaderSpec extends Spec { def is = s2""" instantiate with multiple apply methods if only one returns the correct type $multipleApply instantiate with primary constructor when no apply methods and multiple constructors $multipleConstructors instantiate with camel case fields and hyphen case config keys $withCamelCaseFields - throw an exception with non-exhuastive key mapping $withExhaustivityCheck + throw an exception with non-exhuastive key mapping $withFailingExhaustivityCheck + succeed with an exhuastive key mapping $withPassingExhaustivityCheck use another implicit value reader for a field $withOptionField fall back to a default value on an apply method $fallBackToApplyMethodDefaultValue fall back to default values on an apply method if base key isn't in config $fallBackToApplyMethodDefaultValueNoKey @@ -89,20 +90,29 @@ class ArbitraryTypeReaderSpec extends Spec { def is = s2""" def withCamelCaseFields = { import Ficus.{stringValueReader} - import OtherArbitraryTypeReader._ + import LowercaseArbitraryTypeReader._ val cfg = ConfigFactory.parseString(s"withCamelCaseFields { acamelcasefield: foo, anothercamelcasefield: bar }") val instance = arbitraryTypeValueReader[ClassWithCamelCaseFields].read(cfg, "withCamelCaseFields") (instance.aCamelCaseField must_== "foo") and (instance.anotherCamelCaseField must_== "bar") } - def withExhaustivityCheck = { - import Ficus.{stringValueReader} - import OtherArbitraryTypeReader._ - val cfg = ConfigFactory.parseString(s"withCamelCaseFields { acamelcasefield: foo, anothercamelcasefield: bar, anextrafield: error }") - val reader = arbitraryTypeValueReader[ClassWithCamelCaseFields] - def doRead: Unit = reader.read(cfg, "withCamelCaseFields") + def withPassingExhaustivityCheck = { + import Ficus._ + import ExhaustiveArbitraryTypeReader._ + val cfg = ConfigFactory.parseString(s"withExhaustive { foo: foo, bar: 1 }") + val reader = arbitraryTypeValueReader[ClassWithMultipleParams] + val instance = reader.read(cfg, "withExhaustive") + (instance.foo must_== "foo") and (instance.bar must_== 1) + } + + def withFailingExhaustivityCheck = { + import Ficus._ + import ExhaustiveArbitraryTypeReader._ + val cfg = ConfigFactory.parseString(s"withExhaustive { foo: foo, bar: 1, anextrafield: error }") + val reader = arbitraryTypeValueReader[ClassWithMultipleParams] + def doRead: Unit = reader.read(cfg, "withExhaustive") doRead must throwA[IllegalArgumentException].like { - case e:IllegalArgumentException => e.getMessage should contain("withCamelCaseFields.anextrafield") + case e:IllegalArgumentException => e.getMessage should contain("withExhaustive.anextrafield") } } @@ -273,4 +283,12 @@ object ArbitraryTypeReaderSpec { } class ClassWithCamelCaseFields(val aCamelCaseField: String, val anotherCamelCaseField: String) + + object LowercaseArbitraryTypeReader extends ArbitraryTypeReader { + override def mapParam(n: String) = n.toLowerCase() + } + + object ExhaustiveArbitraryTypeReader extends ArbitraryTypeReader { + override val checkExhaustivity = true + } }