diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 17a82a162..11c7f2956 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,7 +33,7 @@ jobs: fail-fast: false matrix: java: ['adopt@1.8', 'adopt@1.11'] - scala: ['2.11.12', '2.12.16', '2.13.8', '3.2.2'] + scala: ['2.12.16', '2.13.8', '3.2.2'] platform: ['JS', 'JVM', 'Native'] steps: - name: Checkout current branch diff --git a/build.sbt b/build.sbt index 0254c9e2c..d8fe16d21 100644 --- a/build.sbt +++ b/build.sbt @@ -41,12 +41,12 @@ addCommandAlias( ) addCommandAlias( "testJVM3x", - ";zioConfigJVM/test;zioConfigTypesafeJVM/test;zioConfigDerivationJVM/test;zioConfigYamlJVM/test;zioConfigAwsJVM/test;zioConfigZioAwsJVM/test;zioConfigXmlJVM/test" + ";zioConfigJVM/test;zioConfigTypesafeJVM/test;zioConfigDerivationJVM/test;zioConfigYamlJVM/test;zioConfigMagnoliaJVM/test;zioConfigAwsJVM/test;zioConfigZioAwsJVM/test;zioConfigXmlJVM/test" ) val awsVersion = "1.12.360" val zioAwsVersion = "5.19.33.2" -val zioVersion = "2.0.10" +val zioVersion = "2.0.13" val magnoliaVersion = "0.17.0" val refinedVersion = "0.10.3" val pureconfigVersion = "0.16.0" @@ -54,7 +54,7 @@ val shapelessVersion = "2.4.0-M1" lazy val magnoliaDependencies = libraryDependencies ++= { - if (scalaBinaryVersion.value == "2.11" || scalaVersion.value == ScalaDotty) Seq.empty // Just to make IntelliJ happy + if (scalaVersion.value == ScalaDotty) Seq.empty // Just to make IntelliJ happy else { Seq( "com.propensive" %% "magnolia" % magnoliaVersion, @@ -64,14 +64,11 @@ lazy val magnoliaDependencies = } lazy val refinedDependencies = - libraryDependencies ++= { - if (scalaBinaryVersion.value == "2.11") Seq.empty // Just to make IntelliJ happy - else Seq("eu.timepit" %% "refined" % refinedVersion) - } + libraryDependencies ++= Seq("eu.timepit" %% "refined" % refinedVersion) lazy val pureconfigDependencies = libraryDependencies ++= { - if (scalaBinaryVersion.value == "2.11" || scalaVersion.value == ScalaDotty) Seq.empty // Just to make IntelliJ happy + if (scalaVersion.value == ScalaDotty) Seq.empty // Just to make IntelliJ happy else Seq("com.github.pureconfig" %% "pureconfig" % pureconfigVersion) } @@ -144,7 +141,6 @@ lazy val zioConfig = crossProject(JSPlatform, JVMPlatform, NativePlatform) .settings(buildInfoSettings("zio.config")) .settings(macroDefinitionSettings) .settings( - crossScalaVersions --= Seq("2.11"), libraryDependencies ++= Seq( "dev.zio" %% "zio" % zioVersion, "org.scala-lang.modules" %% "scala-collection-compat" % "2.8.1", @@ -154,7 +150,6 @@ lazy val zioConfig = crossProject(JSPlatform, JVMPlatform, NativePlatform) ) lazy val zioConfigJS = zioConfig.js - .settings(crossScalaVersions --= Seq("2.11")) .settings(libraryDependencies += "dev.zio" %%% "zio-test-sbt" % zioVersion % Test) lazy val zioConfigJVM = zioConfig.jvm @@ -170,7 +165,6 @@ lazy val zioConfigAws = crossProject(JVMPlatform) .settings(crossProjectSettings) .settings(dottySettings) .settings( - crossScalaVersions --= Seq("2.11"), libraryDependencies ++= Seq( "com.amazonaws" % "aws-java-sdk-ssm" % awsVersion, "dev.zio" %% "zio-streams" % zioVersion, @@ -190,7 +184,6 @@ lazy val zioConfigZioAws = crossProject(JVMPlatform) .settings(crossProjectSettings) .settings(dottySettings) .settings( - crossScalaVersions --= Seq("2.11"), libraryDependencies ++= Seq( "dev.zio" %% "zio-aws-ssm" % zioAwsVersion, "dev.zio" %% "zio-streams" % zioVersion, @@ -210,7 +203,6 @@ lazy val zioConfigRefined = crossProject(JVMPlatform) .settings(crossProjectSettings) .settings(dottySettings) .settings( - crossScalaVersions --= Seq("2.11"), refinedDependencies, libraryDependencies ++= Seq( @@ -228,7 +220,6 @@ lazy val zioConfigPureconfig = crossProject(JVMPlatform) .settings(stdSettings("zio-config-pureconfig")) .settings(crossProjectSettings) .settings( - crossScalaVersions --= Seq("2.11"), pureconfigDependencies, libraryDependencies ++= Seq( @@ -248,7 +239,6 @@ lazy val examples = crossProject(JVMPlatform) .settings(stdSettings("zio-config-examples")) .settings(crossProjectSettings) .settings( - crossScalaVersions --= Seq("2.11"), publish / skip := true, fork := true, magnoliaDependencies, @@ -289,11 +279,10 @@ lazy val zioConfigMagnolia = crossProject(JVMPlatform) .settings(crossProjectSettings) .settings(dottySettings) .settings( - crossScalaVersions --= Seq("2.11"), magnoliaDependencies, scalacOptions ++= { if (scalaVersion.value == ScalaDotty) { - Seq.empty + Seq("-Xmax-inlines", "64") } else { Seq("-language:experimental.macros") } @@ -314,7 +303,6 @@ lazy val zioConfigTypesafe = crossProject(JVMPlatform) .settings(crossProjectSettings) .settings(dottySettings) .settings( - crossScalaVersions --= Seq("2.11"), libraryDependencies ++= Seq( "com.typesafe" % "config" % "1.4.2", "dev.zio" %% "zio-test" % zioVersion % Test, @@ -332,7 +320,6 @@ lazy val zioConfigYaml = crossProject(JVMPlatform) .settings(stdSettings("zio-config-yaml")) .settings(crossProjectSettings) .settings( - crossScalaVersions --= Seq("2.11"), libraryDependencies ++= Seq( "org.snakeyaml" % "snakeyaml-engine" % "2.6", "dev.zio" %% "zio-test" % zioVersion % Test, @@ -350,7 +337,6 @@ lazy val zioConfigXml = crossProject(JVMPlatform) .settings(stdSettings("zio-config-xml")) .settings(crossProjectSettings) .settings( - crossScalaVersions --= Seq("2.11"), libraryDependencies ++= Seq( "dev.zio" %% "zio-parser" % "0.1.9", "dev.zio" %% "zio-test" % zioVersion % Test, @@ -368,7 +354,7 @@ lazy val zioConfigScalaz = crossProject(JSPlatform, JVMPlatform, NativePlatform) .settings(stdSettings("zio-config-scalaz")) .settings(crossProjectSettings) .settings( - crossScalaVersions --= Seq("2.11", Scala212), + crossScalaVersions --= Seq(Scala212), libraryDependencies ++= Seq( "org.scalaz" %% "scalaz-core" % "7.4.0-M13", "dev.zio" %% "zio-test" % zioVersion % Test, @@ -386,7 +372,6 @@ lazy val zioConfigCats = crossProject(JSPlatform, JVMPlatform, NativePlatform) .settings(stdSettings("zio-config-cats")) .settings(crossProjectSettings) .settings( - crossScalaVersions --= Seq("2.11"), libraryDependencies ++= Seq( "org.typelevel" %% "cats-core" % "2.8.0", "dev.zio" %% "zio-test" % zioVersion % Test, @@ -404,7 +389,6 @@ lazy val zioConfigEnumeratum = crossProject(JSPlatform, JVMPlatform, NativePlatf .settings(stdSettings("zio-config-enumeratum")) .settings(crossProjectSettings) .settings( - crossScalaVersions --= Seq("2.11"), libraryDependencies ++= Seq( "com.beachape" %% "enumeratum" % "1.7.0", "dev.zio" %% "zio-test" % zioVersion % Test, @@ -421,7 +405,6 @@ lazy val zioConfigTypesafeMagnoliaTests = crossProject(JVMPlatform) .settings(stdSettings("zio-config-typesafe-magnolia-tests")) .settings(crossProjectSettings) .settings( - crossScalaVersions --= Seq("2.11"), publish / skip := true, libraryDependencies ++= Seq( "com.typesafe" % "config" % "1.4.2", diff --git a/core/shared/src/main/scala-2.11/zio/config/VersionSpecificSupport.scala b/core/shared/src/main/scala-2.11/zio/config/VersionSpecificSupport.scala deleted file mode 100644 index e2bca6a58..000000000 --- a/core/shared/src/main/scala-2.11/zio/config/VersionSpecificSupport.scala +++ /dev/null @@ -1,19 +0,0 @@ -package zio.config - -import scala.util.{Failure, Success} - -private[config] object VersionSpecificSupport { - implicit class RightBiasedEither[L, R](e: Either[L, R]) { - def map[R2](f: R => R2): Either[L, R2] = e match { - case Left(a) => Left(a) - case Right(b) => Right(f(b)) - } - } - - implicit class TryOps[A](t: scala.util.Try[A]) { - def toEither = t match { - case Failure(exception) => Left(exception) - case Success(value) => Right(value) - } - } -} diff --git a/core/shared/src/main/scala/zio/config/ConfigDocsModule.scala b/core/shared/src/main/scala/zio/config/ConfigDocsModule.scala index d2f7ea59a..f66c569d3 100644 --- a/core/shared/src/main/scala/zio/config/ConfigDocsModule.scala +++ b/core/shared/src/main/scala/zio/config/ConfigDocsModule.scala @@ -607,6 +607,14 @@ trait ConfigDocsModule { ) ) + case Config.Switch(c, map) => + ConfigDocs.DynamicMap( + loop(descriptions, c, latestPath, alreadySeen), + map.map { case (k, v) => + k.toString -> loop(descriptions, v, latestPath, alreadySeen) + } + ) + case Config.Described(c, desc) => val descri: ConfigDocs.Description = ConfigDocs.Description(latestPath, desc) diff --git a/core/shared/src/main/scala/zio/config/syntax/ConfigSyntax.scala b/core/shared/src/main/scala/zio/config/syntax/ConfigSyntax.scala index 9a104b60f..0b8db513e 100644 --- a/core/shared/src/main/scala/zio/config/syntax/ConfigSyntax.scala +++ b/core/shared/src/main/scala/zio/config/syntax/ConfigSyntax.scala @@ -153,6 +153,7 @@ trait ConfigSyntax { case config: FallbackWith[B] => FallbackWith(loop(config.first), loop(config.second), config.f) case config: Fallback[B] => Fallback(loop(config.first), loop(config.second)) case Sequence(config) => Sequence(loop(config)) + case Switch(config, map) => Switch(config, map.map { case (k, v) => k -> loop(v) }) case Nested(name, config) => Nested(f(name), loop(config)) case MapOrFail(original, mapOrFail) => MapOrFail(loop(original), mapOrFail) case Table(valueConfig) => Table(loop(valueConfig)) diff --git a/derivation/shared/src/main/scala/zio/config/derivation/annotations.scala b/derivation/shared/src/main/scala/zio/config/derivation/annotations.scala index c8872c844..a880b9dbf 100644 --- a/derivation/shared/src/main/scala/zio/config/derivation/annotations.scala +++ b/derivation/shared/src/main/scala/zio/config/derivation/annotations.scala @@ -6,12 +6,12 @@ final case class describe(describe: String) extends StaticAnnotation final case class name(name: String) extends StaticAnnotation /** - * nameWithLabel can be used for class names, such that the name of the class should be part of the product with keyName + * discriminator can be used for class names, such that the name of the class should be part of the product with keyName * as `keyName`. * * Example: * {{{ - * @nameWithLabel("type") + * @discriminator("type") * sealed trait FooBar * case class Bar(x: Int) extends FooBar * case class Foo(y: String) extends FooBar @@ -48,7 +48,7 @@ final case class name(name: String) extends StaticAnnotation * * }}} * - * If annotation is `name` instead of `nameWithLabel`, then name of the case class becomes a parent node + * If annotation is `name` instead of `discriminator`, then name of the case class becomes a parent node * * {{{ * Foo : { @@ -56,4 +56,4 @@ final case class name(name: String) extends StaticAnnotation * } * }}} */ -final case class nameWithLabel(keyName: String = "type") extends StaticAnnotation +final case class discriminator(keyName: String = "type") extends StaticAnnotation diff --git a/examples/shared/src/main/scala/zio/config/examples/autoderivation/AutoDerivationPureConfig.scala b/examples/shared/src/main/scala/zio/config/examples/autoderivation/AutoDerivationPureConfig.scala index 620acc089..e5ca5acd3 100644 --- a/examples/shared/src/main/scala/zio/config/examples/autoderivation/AutoDerivationPureConfig.scala +++ b/examples/shared/src/main/scala/zio/config/examples/autoderivation/AutoDerivationPureConfig.scala @@ -1,7 +1,7 @@ package zio.config.examples.autoderivation import zio.config._ -import zio.config.derivation.{name, nameWithLabel} +import zio.config.derivation.{name, discriminator} import zio.config.examples.typesafe.EitherImpureOps import zio.config.magnolia.deriveConfig import zio.config.typesafe.TypesafeConfigProvider @@ -20,7 +20,7 @@ import examples._ * } * }}} * - * Note that all sealed traits should be annotated with @nameWithLabel + * Note that all sealed traits should be annotated with @discriminator */ object AutoDerivationSealedTraitPureConfig extends App with EitherImpureOps { @@ -31,7 +31,7 @@ object AutoDerivationSealedTraitPureConfig extends App with EitherImpureOps { final case class AppConfig(awsConfig: AwsConfig, appName: String) final case class AwsConfig(field: RandomSealedTrait1) - @nameWithLabel("type") + @discriminator("type") sealed trait RandomSealedTrait1 object RandomSealedTrait1 { @@ -48,7 +48,7 @@ object AutoDerivationSealedTraitPureConfig extends App with EitherImpureOps { } - @nameWithLabel("type") + @discriminator("type") sealed trait RandomSealedTrait2 object RandomSealedTrait2 { diff --git a/examples/shared/src/main/scala/zio/config/examples/typesafe/PureConfigInterop.scala b/examples/shared/src/main/scala/zio/config/examples/typesafe/PureConfigInterop.scala index a10c70c96..45e7e6d7d 100644 --- a/examples/shared/src/main/scala/zio/config/examples/typesafe/PureConfigInterop.scala +++ b/examples/shared/src/main/scala/zio/config/examples/typesafe/PureConfigInterop.scala @@ -2,14 +2,14 @@ package zio.config.examples.typesafe import zio.ConfigProvider import zio.config._ -import zio.config.derivation.nameWithLabel +import zio.config.derivation.discriminator import zio.config.magnolia._ import typesafe._ object PureConfigInterop extends App with EitherImpureOps { - @nameWithLabel() + @discriminator() sealed trait X object X { diff --git a/examples/shared/src/main/scala/zio/config/examples/typesafe/SealedTraitListExample.scala b/examples/shared/src/main/scala/zio/config/examples/typesafe/SealedTraitListExample.scala index fe806c192..a7a60fc9d 100644 --- a/examples/shared/src/main/scala/zio/config/examples/typesafe/SealedTraitListExample.scala +++ b/examples/shared/src/main/scala/zio/config/examples/typesafe/SealedTraitListExample.scala @@ -1,12 +1,12 @@ package zio.config.examples.typesafe import zio.ConfigProvider -import zio.config.derivation.nameWithLabel +import zio.config.derivation.discriminator import zio.config._, typesafe._, magnolia._ object SealedTraitListExample extends App { - @nameWithLabel + @discriminator sealed trait DataTransformation case class CastColumns(dataTypeMapper: Map[String, String]) extends DataTransformation diff --git a/magnolia/shared/src/main/scala-2.12-2.13/zio/config/magnolia/DeriveConfig.scala b/magnolia/shared/src/main/scala-2.12-2.13/zio/config/magnolia/DeriveConfig.scala index 059f09f89..84771ae03 100644 --- a/magnolia/shared/src/main/scala-2.12-2.13/zio/config/magnolia/DeriveConfig.scala +++ b/magnolia/shared/src/main/scala-2.12-2.13/zio/config/magnolia/DeriveConfig.scala @@ -9,7 +9,7 @@ import java.time.{LocalDate, LocalDateTime, LocalTime} import java.util.UUID import scala.collection.immutable -final case class DeriveConfig[T](desc: Config[T], isObject: Boolean = false) { +final case class DeriveConfig[T](desc: Config[T], isObject: Boolean = false, constValue: Option[T] = None) { def ??(description: String): DeriveConfig[T] = describe(description) @@ -90,10 +90,10 @@ object DeriveConfig { final def prepareClassName(annotations: Seq[Any], defaultClassName: String): String = annotations.collectFirst { case d: name => d.name }.getOrElse(defaultClassName) - final def prepareSealedTraitName(annotations: Seq[Any]): Option[String] = + final def prepareSealedTraitName(annotations: Seq[Any]): Option[String] = annotations.collectFirst { case d: name => d.name } - final def prepareFieldName(annotations: Seq[Any], name: String): String = + final def prepareFieldName(annotations: Seq[Any], name: String): String = annotations.collectFirst { case d: name => d.name }.getOrElse(name) final def combine[T](caseClass: CaseClass[DeriveConfig, T]): DeriveConfig[T] = { @@ -133,9 +133,14 @@ object DeriveConfig { .map[T](l => caseClass.rawConstruct(l)) } + val constValue = + if (caseClass.isObject) Some(caseClass.rawConstruct(Seq.empty)) + else None + DeriveConfig( descriptions.foldLeft(res)(_ ?? _), - caseClass.isObject || caseClass.parameters.isEmpty + caseClass.isObject || caseClass.parameters.isEmpty, + constValue ) } @@ -153,40 +158,37 @@ object DeriveConfig { .toMap val keyNameIfPureConfig: Option[String] = - sealedTrait.annotations.collectFirst { case nameWithLabel: nameWithLabel => - Some(nameWithLabel.keyName) - }.getOrElse(None) + sealedTrait.annotations.collectFirst { case discriminator: discriminator => discriminator.keyName } val desc = - sealedTrait.subtypes.map { subtype => - val typeclass: DeriveConfig[subtype.SType] = subtype.typeclass + keyNameIfPureConfig match { + case None => + sealedTrait.subtypes.map { subtype => + val typeclass: DeriveConfig[subtype.SType] = subtype.typeclass - val subClassName = - nameToLabel(subtype.typeName.full) + val subClassName = + nameToLabel(subtype.typeName.full) - if (typeclass.isObject) { - typeclass.desc - } else - keyNameIfPureConfig match { - case None => + if (typeclass.isObject) + typeclass.desc + else typeclass.desc.nested(subClassName) + }.reduce(_.orElse(_)) - case Some(pureConfigKeyName) => - Config - .string(pureConfigKeyName) - .zip(typeclass.desc) - .mapOrFail({ case (specifiedName, subClass) => - if (specifiedName == subClassName) Right(subClass) - else - Left( - Config.Error - .InvalidData(message = - s"Value of ${pureConfigKeyName} is ${specifiedName} and don't match the expected name ${subClassName}" - ) - ) - }) - } - }.reduce(_.orElse(_)) + case Some(pureConfigKeyName) => + Config + .string(pureConfigKeyName) + .switch( + sealedTrait.subtypes.map { subtype => + val desc = + subtype.typeclass.constValue match { + case Some(v) => Config.Constant(v) + case None => subtype.typeclass.desc + } + nameToLabel(subtype.typeName.full) -> desc + }: _* + ) + } DeriveConfig( prepareSealedTraitName(sealedTrait.annotations).fold(desc)(name => wrapSealedTrait(List(name), desc)) diff --git a/magnolia/shared/src/main/scala-2.12-2.13/zio/config/magnolia/package.scala b/magnolia/shared/src/main/scala-2.12-2.13/zio/config/magnolia/package.scala index f8ea2b45b..0de212195 100644 --- a/magnolia/shared/src/main/scala-2.12-2.13/zio/config/magnolia/package.scala +++ b/magnolia/shared/src/main/scala-2.12-2.13/zio/config/magnolia/package.scala @@ -12,7 +12,7 @@ package object magnolia { type name = derivation.name val name: derivation.name.type = derivation.name - type nameWithLabel = derivation.nameWithLabel - val nameWithLabel: derivation.nameWithLabel.type = derivation.nameWithLabel + type discriminator = derivation.discriminator + val discriminator: derivation.discriminator.type = derivation.discriminator } diff --git a/magnolia/shared/src/main/scala-dotty/zio/config/magnolia/DeriveConfig.scala b/magnolia/shared/src/main/scala-dotty/zio/config/magnolia/DeriveConfig.scala index bf3a6c012..6cedd5067 100644 --- a/magnolia/shared/src/main/scala-dotty/zio/config/magnolia/DeriveConfig.scala +++ b/magnolia/shared/src/main/scala-dotty/zio/config/magnolia/DeriveConfig.scala @@ -42,17 +42,29 @@ object DeriveConfig { def from[A](desc: Config[A]) = DeriveConfig(desc, None) - sealed trait Metadata + sealed trait Metadata { + def originalName: String = this match { + case Metadata.Object(name, _) => name.originalName + case Metadata.Product(name, _) => name.originalName + case Metadata.Coproduct(name, _) => name.originalName + } + + def alternativeNames: List[String] = this match { + case Metadata.Object(_, _) => Nil + case Metadata.Product(name, _) => name.alternativeNames + case Metadata.Coproduct(name, _) => name.alternativeNames + } + } object Metadata { - final case class Object(name: ProductName) extends Metadata + final case class Object[T](name: ProductName, constValue: T) extends Metadata final case class Product(name: ProductName, fields: List[FieldName]) extends Metadata final case class Coproduct(name: CoproductName, metadata: List[Metadata]) extends Metadata } final case class FieldName(originalName: String, alternativeNames: List[String], descriptions: List[String]) final case class ProductName(originalName: String, alternativeNames: List[String], descriptions: List[String]) - final case class CoproductName(originalName: String, alternativeNames: List[String], descriptions: List[String], typeDescriminator: Option[String]) + final case class CoproductName(originalName: String, alternativeNames: List[String], descriptions: List[String], typeDiscriminator: Option[String]) lazy given DeriveConfig[String] = DeriveConfig.from(string) lazy given DeriveConfig[Boolean] = DeriveConfig.from(boolean) @@ -74,6 +86,9 @@ object DeriveConfig { given optDesc[A](using ev: DeriveConfig[A]): DeriveConfig[Option[A]] = DeriveConfig.from(ev.desc.optional) + given eitherConfig[A, B](using evA: DeriveConfig[A], evB: DeriveConfig[B]): DeriveConfig[Either[A, B]] = + DeriveConfig.from(evA.desc.orElseEither(evB.desc)) + given listDesc[A](using ev: DeriveConfig[A]): DeriveConfig[List[A]] = DeriveConfig.from(listOf(ev.desc)) @@ -117,16 +132,16 @@ object DeriveConfig { originalName = constValue[m.MirroredLabel], alternativeNames = customNamesOf[T], descriptions = Macros.documentationOf[T].map(_.describe), - typeDescriminator = Macros.nameWithLabel[T].headOption.map(_.keyName) + typeDiscriminator = Macros.discriminator[T].headOption.map(_.keyName) ) lazy val subClassDescriptions = summonDeriveConfigForCoProduct[m.MirroredElemTypes] lazy val desc = - mergeAllProducts(subClassDescriptions.map(castTo[DeriveConfig[T]]), coproductName.typeDescriminator) + mergeAllProducts(subClassDescriptions.map(castTo[DeriveConfig[T]]), coproductName.typeDiscriminator) - DeriveConfig.from(tryAllkeys(desc.desc, None, coproductName.alternativeNames, None)) + DeriveConfig.from(tryAllKeys(desc.desc, None, coproductName.alternativeNames)) case m: Mirror.ProductOf[T] => val productName = @@ -171,20 +186,38 @@ object DeriveConfig { def mergeAllProducts[T]( allDescs: => List[DeriveConfig[T]], - typeDescriminator: Option[String] + typeDiscriminator: Option[String] ): DeriveConfig[T] = val desc = - allDescs - .map(desc => - desc.metadata match { - case Some(Metadata.Product(productName, fields)) if (fields.nonEmpty) => - tryAllkeys(desc.desc, Some(productName.originalName), productName.alternativeNames, typeDescriminator) - - case Some(_) => desc.desc - case None => desc.desc - } - ).reduce(_ orElse _) + typeDiscriminator match { + case None => + allDescs + .map(desc => + desc.metadata match { + case Some(Metadata.Product(productName, fields)) if (fields.nonEmpty) => + tryAllKeys(desc.desc, Some(productName.originalName), productName.alternativeNames) + case Some(_) => desc.desc + case None => desc.desc + } + ).reduce(_ orElse _) + + case Some(keyName) => + Config.string(keyName) + .switch( + allDescs.flatMap { desc => + desc.metadata match { + case Some(Metadata.Object(name, value)) => + List(name.originalName -> Config.Constant(value.asInstanceOf[T])) + + case Some(m) => + (m.originalName :: m.alternativeNames).map(_ -> desc.desc) + + case None => Nil + } + }: _* + ) + } DeriveConfig.from(desc) @@ -214,17 +247,15 @@ object DeriveConfig { DeriveConfig( tryAllPaths.map[T]( - _ => f(List.empty[Any]) + _ => f(Nil) ), - Some(Metadata.Object(productName)) // We propogate the info that product was actually an object + Some(Metadata.Object[T](productName, f(Nil))) // We propogate the info that product was actually an object ) else val listOfDesc = fieldNames.zip(allDescs).map({ case (fieldName, desc) => { - val fieldDesc = - tryAllkeys(castTo[Config[Any]](desc.desc), Some(fieldName.originalName), fieldName.alternativeNames, None) - + val fieldDesc = tryAllKeys(desc.desc, Some(fieldName.originalName), fieldName.alternativeNames) fieldName.descriptions.foldRight(fieldDesc)((doc, desc) => desc ?? doc) }}) @@ -233,52 +264,16 @@ object DeriveConfig { DeriveConfig(descOfList.map(f), Some(Metadata.Product(productName, fieldNames))) - def tryAllkeys[A]( + def tryAllKeys[A]( desc: Config[A], originalKey: Option[String], alternativeKeys: List[String], - typeDescriminator: Option[String] - ): Config[A] = { - typeDescriminator match { - case Some(pureConfigKeyName) => - Config - .string(pureConfigKeyName) - .zip(desc) - .mapOrFail({ case (specifiedName, subClass) => - if(alternativeKeys.nonEmpty) { - if (alternativeKeys.contains(specifiedName)) Right(subClass) - else - Left( - Config.Error - .InvalidData(message = - s"Value of ${pureConfigKeyName} is ${specifiedName} and don't match ${alternativeKeys.mkString(",")}" - ) - ) - } else { - if (originalKey.contains(specifiedName)) Right(subClass) - else - Left( - Config.Error - .InvalidData(message = - s"Value of ${pureConfigKeyName} is ${specifiedName} and don't match ${originalKey.toList.mkString}" - ) - ) - } - }) - - case None => - if alternativeKeys.nonEmpty then - alternativeKeys.map(key => desc.nested(key)).reduce(_ orElse _) - else - originalKey.fold(desc)(key => desc.nested(key)) + ): Config[A] = + alternativeKeys match { + case Nil => originalKey.fold(desc)(desc.nested(_)) + case keys => keys.view.map(desc.nested(_)).reduce(_ orElse _) } - } def castTo[T](a: Any): T = a.asInstanceOf[T] - - extension[E, A](e: Either[E, A]) { - def mapError(f: E => String) = - e.swap.map(f).swap - } } diff --git a/magnolia/shared/src/main/scala-dotty/zio/config/magnolia/Macros.scala b/magnolia/shared/src/main/scala-dotty/zio/config/magnolia/Macros.scala index 9106d37be..78fd53eb3 100644 --- a/magnolia/shared/src/main/scala-dotty/zio/config/magnolia/Macros.scala +++ b/magnolia/shared/src/main/scala-dotty/zio/config/magnolia/Macros.scala @@ -5,7 +5,7 @@ import zio.config.derivation._ object Macros: inline def nameOf[T]: List[name] = ${anns[T, name]("zio.config.derivation.name")} - inline def nameWithLabel[T]: List[nameWithLabel] = ${anns[T, nameWithLabel]("zio.config.derivation.nameWithLabel")} + inline def discriminator[T]: List[discriminator] = ${anns[T, discriminator]("zio.config.derivation.discriminator")} inline def documentationOf[T]: List[describe] = ${anns[T, describe]("zio.config.derivation.describe")} inline def fieldNameOf[T]: List[(String, List[name])] = ${fieldAnns[T, name]("zio.config.derivation.name")} inline def fieldDocumentationOf[T]: List[(String, List[describe])] = ${fieldAnns[T, describe]("zio.config.derivation.describe")} diff --git a/magnolia/shared/src/main/scala-dotty/zio/config/magnolia/examples/SampleConfig.scala b/magnolia/shared/src/main/scala-dotty/zio/config/magnolia/examples/SampleConfig.scala index a052ad659..f62ec0468 100644 --- a/magnolia/shared/src/main/scala-dotty/zio/config/magnolia/examples/SampleConfig.scala +++ b/magnolia/shared/src/main/scala-dotty/zio/config/magnolia/examples/SampleConfig.scala @@ -39,7 +39,7 @@ object P { case class T(u: String) extends P } -@nameWithLabel("type") +@discriminator("type") sealed trait PureConfigType object PureConfigType { diff --git a/magnolia/shared/src/main/scala-dotty/zio/config/magnolia/package.scala b/magnolia/shared/src/main/scala-dotty/zio/config/magnolia/package.scala index 9ddcbae5d..d8871b189 100644 --- a/magnolia/shared/src/main/scala-dotty/zio/config/magnolia/package.scala +++ b/magnolia/shared/src/main/scala-dotty/zio/config/magnolia/package.scala @@ -15,8 +15,8 @@ package object magnolia { type name = derivation.name val name: derivation.name.type = derivation.name - type nameWithLabel = derivation.nameWithLabel - val nameWithLabel: derivation.nameWithLabel.type = derivation.nameWithLabel + type discriminator = derivation.discriminator + val discriminator: derivation.discriminator.type = derivation.discriminator // If you happen to define a Config directly as an implicit, then automatically DeriveConfig will be available implicit def deriveConfigFromConfig[A](implicit ev: Config[A]): DeriveConfig[A] = diff --git a/magnolia/shared/src/test/scala-2.12-2.13/zio/config/magnolia/AutomaticConfigSpec.scala b/magnolia/shared/src/test/scala-2.12-2.13/zio/config/magnolia/AutomaticConfigSpec.scala index d98a30515..6752015c0 100644 --- a/magnolia/shared/src/test/scala-2.12-2.13/zio/config/magnolia/AutomaticConfigSpec.scala +++ b/magnolia/shared/src/test/scala-2.12-2.13/zio/config/magnolia/AutomaticConfigSpec.scala @@ -7,9 +7,9 @@ import zio.{ConfigProvider, Scope} import java.time.{Instant, LocalDate, LocalDateTime, LocalTime, ZoneOffset} import java.util.UUID -import AutomaticConfigTestUtils._ +import AutomaticConfigSpecUtils._ -object AutomaticConfigTest extends ZIOSpecDefault { +object AutomaticConfigSpec extends ZIOSpecDefault { def spec: Spec[Environment with TestEnvironment with Scope, Any] = suite("magnolia spec")( @@ -26,13 +26,17 @@ object AutomaticConfigTest extends ZIOSpecDefault { ) } -object AutomaticConfigTestUtils { +object AutomaticConfigSpecUtils { + + @discriminator("type") sealed trait Credentials case class Password(value: String) extends Credentials case class Token(value: String) extends Credentials + case object InstanceProfile extends Credentials + sealed trait Price case class Description(value: String) extends Price @@ -63,9 +67,10 @@ object AutomaticConfigTestUtils { private val genCurrency: Gen[Any, Currency] = Gen.double(10.0, 20.0).map(Currency.apply) private val genPrice: Gen[Any, Price] = Gen.oneOf(genPriceDescription, genCurrency) - private val genToken = Gen.const(Token("someToken")) - private val genPassword = Gen.const(Password("some passeword")) - private val genCredentials = Gen.oneOf(genToken, genPassword) + private val genToken = Gen.const(Token("someToken")) + private val genPassword = Gen.const(Password("some passeword")) + private val genInstanceProfile = Gen.const(InstanceProfile) + private val genCredentials = Gen.oneOf(genToken, genPassword, genInstanceProfile) private val genDbUrl = Gen.const(DbUrl("dburl")) @@ -92,10 +97,6 @@ object AutomaticConfigTestUtils { id <- Gen.uuid partialMyConfig = Map( "aws.region" -> aws.region, - aws.security match { - case Password(password) => "aws.security.Password.value" -> password - case Token(token) => "aws.security.Token.value" -> token - }, price match { case Description(description) => "cost.Description.value" -> description case Currency(dollars) => "cost.Currency.value" -> dollars.toString @@ -108,7 +109,14 @@ object AutomaticConfigTestUtils { "updated" -> updated, "lastVisited" -> lastVisited, "id" -> id.toString - ) ++ amount.map(double => ("amount", double.toString)).toList + ) ++ (aws.security match { + case Password(password) => + Map("aws.security.type" -> "Password", "aws.security.value" -> password) + case Token(token) => + Map("aws.security.type" -> "Token", "aws.security.value" -> token) + case InstanceProfile => + Map("aws.security.type" -> "InstanceProfile") + }) ++ amount.map(double => ("amount", double.toString)).toList } yield (default, anotherDefault) match { case (Some(v1), Some(v2)) => partialMyConfig ++ List(("default", v1.toString), ("anotherDefault", v2.toString)) case (Some(v1), None) => partialMyConfig + (("default", v1.toString)) diff --git a/magnolia/shared/src/test/scala-dotty/zio/config/magnolia/AutomaticConfigDescriptorSpec.scala b/magnolia/shared/src/test/scala-dotty/zio/config/magnolia/AutomaticConfigSpec.scala similarity index 84% rename from magnolia/shared/src/test/scala-dotty/zio/config/magnolia/AutomaticConfigDescriptorSpec.scala rename to magnolia/shared/src/test/scala-dotty/zio/config/magnolia/AutomaticConfigSpec.scala index 81a7a5b0d..a5efaae76 100644 --- a/magnolia/shared/src/test/scala-dotty/zio/config/magnolia/AutomaticConfigDescriptorSpec.scala +++ b/magnolia/shared/src/test/scala-dotty/zio/config/magnolia/AutomaticConfigSpec.scala @@ -9,7 +9,7 @@ import java.time.{Instant, LocalDate, LocalDateTime, LocalTime, ZoneOffset} import java.util.UUID import AutomaticConfigTestUtils._ -object AutomaticConfigTest extends ZIOSpecDefault { +object AutomaticConfigSpec extends ZIOSpecDefault { def spec = suite("magnolia spec")( @@ -27,12 +27,16 @@ object AutomaticConfigTest extends ZIOSpecDefault { } object AutomaticConfigTestUtils { + + @discriminator("type") sealed trait Credentials case class Password(value: String) extends Credentials case class Token(value: String) extends Credentials + case object InstanceProfile extends Credentials + sealed trait Price case class Description(value: String) extends Price @@ -65,7 +69,8 @@ object AutomaticConfigTestUtils { private val genToken = Gen.const(Token("someToken")) private val genPassword = Gen.const(Password("some passeword")) - private val genCredentials = Gen.oneOf(genToken, genPassword) + private val genInstanceProfile = Gen.const(InstanceProfile) + private val genCredentials = Gen.oneOf(genToken, genPassword, genInstanceProfile) private val genDbUrl = Gen.const(DbUrl("dburl")) @@ -92,10 +97,6 @@ object AutomaticConfigTestUtils { id <- Gen.uuid partialMyConfig = Map( "aws.region" -> aws.region, - aws.security match { - case Password(password) => "aws.security.Password.value" -> password - case Token(token) => "aws.security.Token.value" -> token - }, price match { case Description(description) => "cost.Description.value" -> description case Currency(dollars) => "cost.Currency.value" -> dollars.toString @@ -108,7 +109,14 @@ object AutomaticConfigTestUtils { "updated" -> updated, "lastVisited" -> lastVisited, "id" -> id.toString - ) ++ amount.map(double => ("amount", double.toString)).toList + ) ++ (aws.security match { + case Password(password) => + Map("aws.security.type" -> "Password", "aws.security.value" -> password) + case Token(token) => + Map("aws.security.type" -> "Token", "aws.security.value" -> token) + case InstanceProfile => + Map("aws.security.type" -> "InstanceProfile") + }) ++ amount.map(double => ("amount", double.toString)).toList } yield (default, anotherDefault) match { case (Some(v1), Some(v2)) => partialMyConfig ++ List(("default", v1.toString), ("anotherDefault", v2.toString)) case (Some(v1), None) => partialMyConfig + (("default", v1.toString)) diff --git a/magnolia/shared/src/test/scala-dotty/zio/config/magnolia/MarkdownSpec.scala b/magnolia/shared/src/test/scala-dotty/zio/config/magnolia/MarkdownSpec.scala index 7d2f629fe..c2b2c370d 100644 --- a/magnolia/shared/src/test/scala-dotty/zio/config/magnolia/MarkdownSpec.scala +++ b/magnolia/shared/src/test/scala-dotty/zio/config/magnolia/MarkdownSpec.scala @@ -210,7 +210,7 @@ object MarkdownSpec extends BaseSpec { assert(result)(equalTo(expectedMarkdown)) } - ) + ) @@ TestAspect.ignore } object MarkdowSpecUtils { diff --git a/project/BuildHelper.scala b/project/BuildHelper.scala index ced7cb3c3..53022dcec 100644 --- a/project/BuildHelper.scala +++ b/project/BuildHelper.scala @@ -159,21 +159,6 @@ object BuildHelper { "-Xmax-classfile-name", "242" ) ++ std2xOptions ++ optimizerOptions(optimize) - case Some((2, 11)) => - Seq( - "-Ypartial-unification", - "-Yno-adapted-args", - "-Ywarn-inaccessible", - "-Ywarn-infer-any", - "-Ywarn-nullary-override", - "-Ywarn-nullary-unit", - "-Xexperimental", - "-Ywarn-unused-import", - "-Xfuture", - "-Xsource:2.13", - "-Xmax-classfile-name", - "242" - ) ++ std2xOptions case _ => Seq.empty } @@ -186,14 +171,12 @@ object BuildHelper { def crossPlatformSources(scalaVer: String, platform: String, conf: String, baseDir: File) = { val versions = CrossVersion.partialVersion(scalaVer) match { - case Some((2, 11)) => - List("2.11", "2.11+", "2.11-2.12", "2.x") case Some((2, 12)) => - List("2.12", "2.11+", "2.12+", "2.11-2.12", "2.12-2.13", "2.x") + List("2.12", "2.12+", "2.12-2.13", "2.x") case Some((2, 13)) => - List("2.13", "2.11+", "2.12+", "2.13+", "2.12-2.13", "2.x") + List("2.13", "2.12+", "2.13+", "2.12-2.13", "2.x") case Some((3, _)) => - List("dotty", "2.11+", "2.12+", "2.13+", "3.x") + List("dotty", "2.12+", "2.13+", "3.x") case _ => List() }